Download Reference Manual
The Developer's Library for D
About Wiki Forums Source Search Contact

How to redirect stdout to buffer

Moderators: larsivi kris

Posted: 08/21/07 07:12:24

Hi,

is there any way to implement something like PHP's ob_start/ob_end with tango? In other words how to redirect stdout to buffer?

Thanks, bobef

Author Message

Posted: 08/21/07 08:20:27

yes, there's a few options:

1) use an instance of tango.io.Print to do your formatting directly to any stream/buffer you like. Print needs an OutputStream? and a Layout instance, where the latter can either be constructed independently or stolen from Stdout.layout(). Stdout is actually an instance of Print, and uses a default Layout without fancy I18N support.

2) redirect the console, using Cout.divert() to switch to another conduit (or a reconfigured one). For example, you can point Cout at a FileConduit? or a SocketConduit?, or whatever. This option is currently in SVN only

Hope that helps! (and thx for the bug report on FilePath?)

Posted: 08/21/07 09:03:31

Thanks, the second method is what I need.

Posted: 10/17/07 11:43:22

Hi,

now I am trying to use Cout.divert, but it isn't working. It is for my scripting project. This little program is (trying to) execute each script withing the testsuite directory and compare the results with predefined ones. Here is the code I use to redirect the console:

//save the original cout
auto ogstream=Cout.stream.conduit;
auto redirected=Cout.redirected;
//output some stuff do some other stuff
//...
//flush the cout and divert to buffer
Cout.flush;
Cout.divert((new GrowBuffer).conduit,true);
//do some stuff output some stuff (to the buffer?)
//...
//flush the buffer and read its contents, then return divert back to the original stream
Cout.flush;
char[] result=cast(char[])(cast(Buffer)Cout.stream).slice;
Cout.divert(ogstream,redirected);
//output some more stuff to the user

Now it works. The output is diverted and read in 'result', but the problem is it is also printed to the console. All my printing is done via Cout("asd");

And the whole program:

void main()
{
	auto ogstream=Cout.stream.conduit;
	auto redirected=Cout.redirected;
	void scanner(char[] path, char[] file, bool dir)
	{
		if(dir) return;
		scope auto fp=new FilePath(file);
		if(fp.ext!="fs") return;
		scope auto watch=new StopWatch;
		try
		{
			Cout(file~" ... ");
			scope auto f=new File(fp.name~".txt");
			char[] buf=cast(char[])f.read();
			Cout.flush;
			Cout.divert((new GrowBuffer).conduit,true);

			watch.start();
			scope auto ctx=new DProgram(file);
			ctx.run();
			auto time=watch.stop();

			Cout.flush;
			char[] result=cast(char[])(cast(Buffer)Cout.stream).slice;
			Cout.divert(ogstream,redirected);
			if(result!=buf)
			{
				Cout("failed\noutput differs from the expected one\n////////expected:\n");
				Cout(buf);
				Cout("\n////////actual:\n");
				Cout(result);
			}
			else Cout("good "~tango.text.convert.Float.toUtf8(time)~"\n");
		}
		catch(Object o)
		{
			Cout.divert(ogstream,redirected);
			Cout("failed\nunhandled exception: "~o.toUtf8~"\n");
		}
	}

	Cout("Running test suite...\n");
	auto fp=FileSystem.toAbsolute(new FilePath("testsuite"));
	FileSystem.setDirectory(fp.toUtf8);
	fp.toList(&scanner);
}

And the actual output is:

Running test suite...
0001.fs ... samplegood 0.00
0002.fs ... samplegood 0.00

"sample" is not supposed to be printed to the console, but only to the buffer. And I will debug why the timer isn't working later :) Oh, and I am using tango 0.99.2 with dmd 1.022.

Can I get some help, please? Thanks, bobef

Posted: 10/17/07 16:18:47

Is "new DProgram()" another process? I'm trying to understand what is being diverted

Posted: 10/17/07 16:24:26

StopWatch? returns an Interval, which is in units of seconds. You can't see the time expended because (a) it is less than 10ms (b) the Float.toUtf8() defaults to 2 decimal places. If you specify, say, 6 decimal places then you will see more information.

(fwiw, I'd also suggest using 'renaming imports' to eliminate those package prefixes: import Float = tango.text.convert.Float;)

Posted: 10/17/07 17:42:42

No, "new DProgram" is not new process, nor new thread. It compiles a script. And the script only contains "print('sample');", and print uses Cout... So I am diverting Cout :)

Posted: 10/17/07 19:02:52

ok, thanks.

I think I see a couple of possible problems. First thing I'd suggest is to move to the newer release, because it simplifies Cout.divert() specifically (used a stream instead of a conduit). The changes to your code would look something like this:

import Float = tango.text.convert.Float;

void main()
{
	auto ogstream=Cout.stream;
	auto redirected=Cout.redirected;

	void scanner(char[] path, char[] file, bool dir)
	{
		if(dir) return;
		auto fp = FilePath(file);
		if(fp.ext!="fs") return;
		StopWatch watch;
		try
		{
                        auto buffer = new GrowBuffer;
			Cout(file~" ... ").flush;
                        Cout.divert(buffer, true);
			auto buf = cast(char[]) File(fp.ext("txt")).read;
			watch.start;
			auto ctx=new DProgram(file);
			ctx.run();
			auto time=watch.stop;
			Cout.flush;
			Cout.divert(ogstream,redirected);
			auto result= cast(char[]) buffer.slice;
			if(result!=buf)
			{
				Cout("failed\noutput differs from the expected one\n////////expected:\n");
				Cout(buf);
				Cout("\n////////actual:\n");
				Cout(result);
			}
			else Cout("good ") (Float.toUtf8(time, 6)).newline;
		}
		catch(Object o)
		{
			Cout.divert(ogstream,redirected);
			Cout("failed\nunhandled exception: ")(o).newline;
		}
	}

	Cout("Running test suite...").newline;
	auto fp=FileSystem.toAbsolute(new FilePath("testsuite"));
	FileSystem.setDirectory(fp.toUtf8);
	fp.toList(&scanner);
}

I think what may have happened is this: your original code was using (new GrowBuffer).conduit as an argument, and the value of that would have been null (no conduit attached to a new buffer). A subsequent Cout.flush would probably leave the accumulated output in the Console output buffer, resulting in what you see. I'm not certain of this, but it seems like a reasonable progression :)

This is partly why the divert() arguments were changed in the recent release: it further simplifies things. Let us know how you get on?

Posted: 10/18/07 09:50:04

Hmmm... now it is working even less. I've updated to tango svn as you suggested. First of all this line "auto buf = cast(char[]) File(fp.ext("txt")).read;" is not compiling. It says that fp.ext takes 0 parameters not 1. Anyway I used my old one for this and the rest compiles, but is not working. When the function "scanner" is called second time (for the second file) it goes in infinite loop inside "Cout(file~" ... ").flush;". If I comment the line where the Cout is diverted back to the original stream ("Cout.divert(ogstream,redirected);") there is no problem and the program completes, but of course I can't see any output after the first divert... And this problem comes from tango, because I've commented my code ("new dprogram..."). I would debug deeper myself but I am not familiar with Tango's internals (or even externals :).

Thank you for your good support

Posted: 10/18/07 17:32:55

Hrm ... that's really strange. I won't be able to look at it for another 8 or 9 hours, but will track it down before tomorrow. Sorry for the delay :)

Posted: 10/19/07 05:56:46

Ach ... there's a nasty bug that I'll need a little time to fix. In the meantime, perhaps you could use this instead of Cout.divert():

auto tmp = Stdout.stream;
auto buffer = new GrowBuffer;
Stdout.stream = buffer;
ctx.run();
Stdout.stream = tmp;

That's assuming your ctx programs are using Stdout instead of Cout ...

Posted: 10/20/07 19:35:12

Thanks again. I am not in a hurry, so I will wait for the bug fix.

Posted: 10/22/07 05:31:09 -- Modified: 10/22/07 05:35:48 by
kris

ok, the update is checked in. I reverted Console back to using conduit instead of stream (like it was before I asked you to refresh from svn). Thus, the save/change/restore would look something like this:

auto prior = Cout.conduit;
Cout.conduit = new MemoryConduit;
...
...
Cout.conduit = prior;

The above will capture all of Cout, Stdout, and whatever else is dependent upon those. In order to improve efficiency, you might also try redirecting the console from outside of the callback (within the main part of your program instead) ? I added a MemoryConduit.clear() method to assist in this kind of approach ...

Also, you might consider using Cerr for error messages while Cout is redirected: Cerr is also routed to the console, but is not captured when Cout is redirected (they have distinct streams).

Oh, and thanks for being so patient :)

Posted: 10/22/07 10:14:41

Now it works, thanks. Plus this Cout.conduit setter/getter is much easier to use than before.

Posted: 10/22/07 16:24:35

oh, that's good :)

Posted: 10/25/07 16:36:02

This has been changed slightly in SVN, so I thought I'd let you know:

auto prior = Cout.output;
Cout.output = new GrowBuffer;
...
...
Cout.output = prior;