Hello dear community!
I want to encapsulate some code's returned stdout and grab it for later (not stream to output). I am kind of un-knowledgable when it comes to the stdout and application streaming. I came up with the following:
import streams
const tempStdoutFileName = "stdout_tmp.txt"
var outputs = newSeq[string]()
template capsule(subject: string) =
stdout.write subject & ": " #no newline
var old = stdout
var caught = open(tempStdoutFileName, fmWrite)
stdout = caught #redirect
# redirect stdout
# (placeholder for more complex logic with try-except and stuff)
echo "Run, " & subject & ", run!"
stdout = old #reset
echo "Check!"
# is there a more elegant solution for the following three lines?
assert caught.reopen(tempStdoutFileName, fmRead)
outputs.add(caught.readAll())
caught.close()
#do the things:
capsule "foo_bar"
capsule "foo_foo"
capsule "bar_bar"
echo outputs
This results in the wanted behavior.
foo_bar: Check!
foo_foo: Check!
bar_bar: Check!
@["Run, foo_bar, run!\n", "Run, foo_foo, run!\n", "Run, bar_bar, run!\n"]
But:
Thanks, a lot!
Hello,
There was a question stackoverflow regarding something very similar : https://stackoverflow.com/questions/64026829/how-to-temporarily-capture-stdout/64032172#64032172
The main take is that you need take stdout using posix function dup / dup2 if you want to be able to safely capture stdout and restore it.
Thanks, @Clonk for the quick reply! Appreciate it!
If I am not mistaken, the SO answer does the same as my code just with reusing unistd.h instead of writing it "pure Nim". (Isn't my solution more platform independent?)
They are also saving to file. And instead of using reopen() proc, they close() and open() it to access the content.
Maybe I am blind, but I feel like the link doesn't answer any of my questions, does it?
If I remember correctly the issues I had when asking the question, I was not able to make a pure Nim solution work on windows giving back control to stdout (I suspect yours might not work on windows, I will try). Instead the solution by @clonk, although in principle should be only posix compatible, works perfectly on my windows 10.
Regarding avoiding the passage from a file, I did not find a way to do it with streams, I too would be interested in a solution (I am currently using package tempfile to create a temporary file).
So, I fiddled around a little. First things first: I found dup and dup2 in the MingW sources. Is this why it is working out of the box when compiling from MacOS to Windows? Anyways, your solution - with importing the dups - does the thing for windows, too :)
Consider the lovely phrase from the Nim docs about getFileHandle:
Note that on Windows this doesn't return the Windows-specific handle, but the C library's notion of a handle, whatever that means.
I can just "target" Nim's FileHandle from stdout.getFileHandle() all the time (refer to example code).
const tempStdoutFileName = "stdout_temp.txt"
proc dup(oldfd: FileHandle): FileHandle {.importc, header: "unistd.h".}
proc dup2(oldfd: FileHandle, newfd: FileHandle): cint {.importc, header: "unistd.h".}
echo "before..."
let caught = open(tempStdoutFileName, fmWrite)
let oldStdoutHandle = dup(stdout.getFileHandle())
discard dup2(caught.getFileHandle(), stdout.getFileHandle())
#body
echo "Run, Forest, run!"
caught.flushFile()
discard dup2(oldStdoutHandle, stdout.getFileHandle())
caught.close()
echo "...after"
echo readFile(tempStdoutFileName)
And a good read (at least for me while trying to understand): SO explanation of file descriptors and SO explanation of dup. ( No aversion, here :D )
Yes, Windows added some posix function to avoid portability issues. I think the Windows function are actually _dup and _dup2 (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/dup-dup2?view=vs-2019).
If people are interested in this, maybe there should be dupFileHandle and dup2FileHandle in system/io to makes things easier ?
Maybe they should even be called duplicateFileHandle(handle) and assignFileHandle(source, target).
I made a helper for my project now that can do setStdout(file) and resetStdout(). And same for std error and input. (I like descriptive names and simple interfaces :D )