mirror of
https://github.com/nim-lang/Nim.git
synced 2026-04-19 22:10:33 +00:00
Add C function dup and dup2 posix to system/io (#15675)
* * Add handle to dup and dup2 posix as duplicate and duplicateTo in std/ioutils. * Added small test & changelog entry * Fixed import in tioutils removed when isMainModule * * Nest test inside block. Rename proc var -> let in captureStdout * Renamed tmpfile to iotuils.txt * Added block: # duplicate, duplicateTo * Improved docstring * Clean non-idiomatic code * Added runnable examples * rm 2 trailing space in expected output * Made syntax prettier * Runnable example: file in getTempDir() * Tmp -> Temp * Fixed runnableExamples on windows
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
|
||||
- `prelude` now works with the JavaScript target.
|
||||
|
||||
- Added `ioutils` module containing `duplicate` and `duplicateTo` to duplicate `FileHandle` using C function `dup` and `dup2`.
|
||||
|
||||
|
||||
## Language changes
|
||||
|
||||
@@ -18,4 +20,3 @@
|
||||
|
||||
|
||||
## Tool changes
|
||||
|
||||
|
||||
85
lib/std/ioutils.nim
Normal file
85
lib/std/ioutils.nim
Normal file
@@ -0,0 +1,85 @@
|
||||
when defined(windows):
|
||||
proc c_dup(oldfd: FileHandle): FileHandle {.
|
||||
importc: "_dup", header: "<io.h>".}
|
||||
proc c_dup2(oldfd: FileHandle, newfd: FileHandle): cint {.
|
||||
importc: "_dup2", header: "<io.h>".}
|
||||
else:
|
||||
proc c_dup(oldfd: FileHandle): FileHandle{.
|
||||
importc: "dup", header: "<unistd.h>".}
|
||||
proc c_dup2(oldfd: FileHandle, newfd: FileHandle): cint {.
|
||||
importc: "dup2", header: "<unistd.h>".}
|
||||
|
||||
# when false:
|
||||
# const SupportIoctlInheritCtl = (defined(linux) or defined(bsd)) and
|
||||
# not defined(nimscript)
|
||||
# when SupportIoctlInheritCtl:
|
||||
# var
|
||||
# FIOCLEX {.importc, header: "<sys/ioctl.h>".}: cint
|
||||
# FIONCLEX {.importc, header: "<sys/ioctl.h>".}: cint
|
||||
|
||||
## Also defined in std/posix and system/io
|
||||
proc strerror(errnum: cint): cstring {.importc, header: "<string.h>".}
|
||||
when not defined(nimscript):
|
||||
var errno {.importc, header: "<errno.h>".}: cint ## error variable
|
||||
|
||||
template checkError(ret: cint) =
|
||||
if ret == -1:
|
||||
when not defined(nimscript):
|
||||
raise newException(IOError, $strerror(errno))
|
||||
else:
|
||||
doAssert(false)
|
||||
|
||||
proc duplicate*(oldfd: FileHandle): FileHandle =
|
||||
##[
|
||||
Return a copy of the file handle `oldfd`.
|
||||
After a successful return, both `FileHandle` may be used interchangeably.
|
||||
They refer to the same open file description and share file offset and status flags.
|
||||
Calls POSIX function `dup` on Posix platform and `_dup` on Windows
|
||||
]##
|
||||
runnableExamples:
|
||||
# stdoutDuplicate is a copy of stdout FileHandle that points to STDOUT
|
||||
let stdoutDuplicate = duplicate(stdout.getFileHandle())
|
||||
# Writing to stdoutDuplicate will write to stdout
|
||||
doAssert(stdoutDuplicate != stdout.getFileHandle())
|
||||
# On windows, opening a file from a FileHandle does not work
|
||||
when not defined(windows):
|
||||
var f : File
|
||||
let res = open(f, stdoutDuplicate, mode=fmWrite)
|
||||
let msg = "This is a test message that will be displayed ! \n"
|
||||
f.write(msg)
|
||||
# Output "Test"
|
||||
f.close()
|
||||
|
||||
result = c_dup(oldfd)
|
||||
checkError(result)
|
||||
|
||||
proc duplicateTo*(oldfd: FileHandle, newfd: FileHandle) =
|
||||
##[
|
||||
Perform the same task a `duplicate` but instead of using the lowest unused file descriptor
|
||||
it uses the FileHandle` specified by `newfd`.
|
||||
Calls POSIX function `dup2` on Posix platform and `_dup2` on Windows.
|
||||
]##
|
||||
runnableExamples:
|
||||
import os
|
||||
# Redirect stdout to a file temporarily
|
||||
let tmpFileName = getTempDir() / "hidden_output.txt"
|
||||
let stdoutFileno = stdout.getFileHandle()
|
||||
let stdoutDupFd = duplicate(stdoutFileno)
|
||||
|
||||
# Create a new file
|
||||
let tmpFile: File = open(tmpFileName, fmAppend)
|
||||
let tmpFileFd: FileHandle = tmpFile.getFileHandle()
|
||||
|
||||
# stdoutFileno now writes to tmpFile
|
||||
duplicateTo(tmpFileFd, stdoutFileno)
|
||||
echo "This is not displayed, but written to tmpFile instead !"
|
||||
|
||||
# Close file & restore stdout
|
||||
tmpFile.close()
|
||||
duplicateTo(stdoutDupFd, stdoutFileno)
|
||||
|
||||
# stdout is now restored !
|
||||
echo "This is displayed"
|
||||
|
||||
let retValue = c_dup2(oldfd, newfd)
|
||||
checkError(retValue)
|
||||
49
tests/stdlib/tioutils.nim
Normal file
49
tests/stdlib/tioutils.nim
Normal file
@@ -0,0 +1,49 @@
|
||||
discard """
|
||||
output: '''
|
||||
hello1
|
||||
hello1
|
||||
'''
|
||||
"""
|
||||
|
||||
import std/ioutils
|
||||
import os
|
||||
from stdtest/specialpaths import buildDir
|
||||
|
||||
block: # duplicate, duplicateTo
|
||||
let tmpFileName = buildDir / "tioutils.txt"
|
||||
template captureStdout(body) : untyped =
|
||||
let stdoutFileno = stdout.getFileHandle()
|
||||
# Duplicate stoudFileno
|
||||
let stdout_dupfd = duplicate(stdoutFileno)
|
||||
# Create a new file
|
||||
# You can use append strategy if you'd like
|
||||
let tmpFile: File = open(tmpFileName, fmWrite)
|
||||
# Get the FileHandle (the file descriptor) of your file
|
||||
let tmpFileFd: FileHandle = tmpFile.getFileHandle()
|
||||
# dup2 tmpFileFd to stdoutFileno -> writing to stdoutFileno now writes to tmpFile
|
||||
duplicateTo(tmpFileFd, stdoutFileno)
|
||||
body
|
||||
# Force flush
|
||||
tmpFile.flushFile()
|
||||
# Close tmp
|
||||
tmpFile.close()
|
||||
# Read tmp
|
||||
let ret = readFile(tmpFileName)
|
||||
# Restore stdout
|
||||
duplicateTo(stdout_dupfd, stdoutFileno)
|
||||
ret
|
||||
|
||||
proc duplicateStdout() =
|
||||
var msg = "hello"
|
||||
echo msg & "1"
|
||||
|
||||
let s = captureStdout:
|
||||
echo msg & "2"
|
||||
|
||||
doAssert s == "hello2\n"
|
||||
|
||||
discard tryRemoveFile(tmpFileName)
|
||||
duplicateStdout()
|
||||
# Check it works twice
|
||||
duplicateStdout()
|
||||
doAssert tryRemoveFile(tmpFileName)
|
||||
Reference in New Issue
Block a user