Cómo capturar (temporalmente) stdout

Sep 23 2020

¿Cómo capturo temporalmente stdout en Nim?

Me gustaría tener una plantilla con la siguiente firma:

template captureStdout(ident: untyped, body: untyped) = discard

De tal manera que este código ( main.nim) se ejecuta sin error:

var msg = "hello"
echo msg & "1"
var s: string
captureStdout(s):
  echo msg & "2"
  msg = "ciao"
echo msg & "3"
assert s == "hello2\n"

y la salida debe ser:

hello1
ciao3

esfuerzos actuales

actualmente puedo capturar stdout usando un archivo temporal, pero no puedo volver a liberarlo. Hago esto con lo siguiente:

template captureStdout*(ident: untyped, body: untyped) =
  discard reopen(stdout, tmpFile, fmWrite)
  body
  ident = readFile(tmpFile)

con esto se main.nimejecuta sin error de afirmación, pero la salida es solo

hello1

y en tmpFile veo:

hello2
ciao3

Respuestas

3 Clonk Sep 23 2020 at 16:16

Cuando llama reopen, reasigna la variable stdouta una Fileque escribe tmpFile.

Para imprimir la salida al sistema STDOUT, necesita reasignar la variable stdouta una Fileque escribe en su sistema STDOUT.

Por tanto, la respuesta difiere entre Linux y Windows.

Para Linux, la forma de hacer esto es usar la función dup y dup2 C para duplicar el stdoutdescriptor de archivo y usar un archivo diferente (para que pueda restaurar stdout). Dado que dup y dup2 no están system/ioen Nim, necesitaremos tener enlaces unistd.h.

He aquí un ejemplo:

#Create dup handles
proc dup(oldfd: FileHandle): FileHandle {.importc, header: "unistd.h".}
proc dup2(oldfd: FileHandle, newfd: FileHandle): cint {.importc,
    header: "unistd.h".}

# Dummy filename
let tmpFileName = "tmpFile.txt"

template captureStdout*(ident: untyped, body: untyped) =
  var stdout_fileno = stdout.getFileHandle()
  # Duplicate stoud_fileno
  var stdout_dupfd = dup(stdout_fileno)
  echo stdout_dupfd
  # Create a new file
  # You can use append strategy if you'd like
  var tmp_file: File = open(tmpFileName, fmWrite)
  # Get the FileHandle (the file descriptor) of your file
  var tmp_file_fd: FileHandle = tmp_file.getFileHandle()
  # dup2 tmp_file_fd to stdout_fileno -> writing to stdout_fileno now writes to tmp_file
  discard dup2(tmp_file_fd, stdout_fileno)
  #
  body
  # Force flush
  tmp_file.flushFile()
  # Close tmp
  tmp_file.close()
  # Read tmp
  ident = readFile(tmpFileName)
  # Restore stdout
  discard dup2(stdout_dupfd, stdout_fileno)

proc main() =
  var msg = "hello"
  echo msg & "1"
  var s: string

  captureStdout(s):
    echo msg & "2"
    msg = "ciao"

  echo msg & "3"
  echo ">> ", s
  assert s == "hello2\n"

when isMainModule:
  main()
  # Check it works twice
  main()