Output to results panel through Stream?

My understanding is that among the many things LINQPad does, it creates a TextWriter to allow outputting to the results panel and sets it as the System.Console.Out and System.Console.Error writers. It doesn't expose a Stream that could output directly to it. This usually isn't a problem since we usually want to output text to the results panel, however some libraries only operate on streams.

One such library is IronPython. Output is written directly to the output streams using the unicode encoding.

#:sdk Microsoft.NET.Sdk

#:package IronPython@3.4.2

#:property PublishAot=false

var py = IronPython.Hosting.Python.CreateEngine();
py.SetSearchPaths([AppDomain.CurrentDomain.BaseDirectory]);

py.Execute("""
print('hello world')
""");

This outputs nothing. Quick workaround would be to redirect to a memory stream and output the contents after execution.

var py = IronPython.Hosting.Python.CreateEngine();
py.SetSearchPaths([AppDomain.CurrentDomain.BaseDirectory]);
var output = new MemoryStream();
py.Runtime.IO.SetOutput(output, Encoding.Unicode);

try
{
    py.Execute("""
    print('hello world')
    """);
}
finally
{
    Encoding.Unicode.GetString(output.ToArray()).Dump();
}

Otherwise, it would be ideal if there was a Stream implementation to output with.

Does an implementation exist? Can this be added?

Comments

  • LINQPad's output works at the TextWriter level, not the Stream level.

    You could #load a script with an adapter such as the following (written by Opus):

    public class TextWriterStream : Stream
    {
        private readonly TextWriter _writer;
        private readonly Encoding _encoding;
    
        public TextWriterStream(TextWriter writer, Encoding? encoding = null)
        {
            _writer = writer ?? throw new ArgumentNullException(nameof(writer));
            _encoding = encoding ?? Encoding.UTF8;
        }
    
        public override bool CanRead => false;
        public override bool CanSeek => false;
        public override bool CanWrite => true;
    
        public override long Length => throw new NotSupportedException();
        public override long Position
        {
            get => throw new NotSupportedException();
            set => throw new NotSupportedException();
        }
    
        public override void Flush() => _writer.Flush();
        public override Task FlushAsync(CancellationToken ct) => _writer.FlushAsync(ct);
    
        public override int Read(byte[] buffer, int offset, int count)
            => throw new NotSupportedException();
    
        public override long Seek(long offset, SeekOrigin origin)
            => throw new NotSupportedException();
    
        public override void SetLength(long value)
            => throw new NotSupportedException();
    
        public override void Write(byte[] buffer, int offset, int count)
        {
            var text = _encoding.GetString(buffer, offset, count);
            _writer.Write(text);
        }
    
        public override void Write(ReadOnlySpan<byte> buffer)
        {
            var text = _encoding.GetString(buffer);
            _writer.Write(text);
        }
    
        public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken ct = default)
        {
            var text = _encoding.GetString(buffer.Span);
            await _writer.WriteAsync(text.AsMemory(), ct);
        }
    }
    
  • I confirm it works like a charm

    var py = IronPython.Hosting.Python.CreateEngine();
    py.SetSearchPaths([AppDomain.CurrentDomain.BaseDirectory]);
    
    using var writerStream = new TextWriterStream(Console.Out, Encoding.Unicode);
    py.Runtime.IO.SetOutput(writerStream, Encoding.Unicode);
    
    py.Execute("""
        print('hello world')
        """);
    
    internal class TextWriterStream(TextWriter writer, Encoding? encoding = null) : Stream {
        public override bool CanRead => throw new NotImplementedException();
        public override bool CanSeek => false;
        public override bool CanWrite => true;
        public override long Length => throw new NotImplementedException();
        public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
        public override void Flush() => writer.Flush();
        public override int Read(byte[] buffer, int offset, int count) => throw new NotImplementedException();
        public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
        public override void SetLength(long value) => throw new NotImplementedException();
        public override void Write(byte[] buffer, int offset, int count) => writer.Write((encoding ?? Encoding.UTF8).GetString(buffer, offset, count));
    }
    
  • Ah thanks. For some reason I thought that making a stream adapter from a TextWriter would have been complex, actually the other way around.