refactor os.nim and ospaths.nim

This commit is contained in:
Andreas Rumpf 2017-09-01 10:35:50 +02:00
commit 50666a1f8b
4 changed files with 852 additions and 847 deletions

159
lib/pure/includes/osenv.nim Normal file
View file

@ -0,0 +1,159 @@
## Include file that implements 'getEnv' and friends. Do not import it!
when not declared(ospaths):
{.error: "This is an include file for ospaths.nim!".}
proc c_getenv(env: cstring): cstring {.
importc: "getenv", header: "<stdlib.h>".}
proc c_putenv(env: cstring): cint {.
importc: "putenv", header: "<stdlib.h>".}
# Environment handling cannot be put into RTL, because the ``envPairs``
# iterator depends on ``environment``.
var
envComputed {.threadvar.}: bool
environment {.threadvar.}: seq[string]
when defined(windows):
# because we support Windows GUI applications, things get really
# messy here...
when useWinUnicode:
when defined(cpp):
proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.
importcpp: "(NI16*)wcschr((const wchar_t *)#, #)", header: "<string.h>".}
else:
proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.
importc: "wcschr", header: "<string.h>".}
else:
proc strEnd(cstr: cstring, c = 0'i32): cstring {.
importc: "strchr", header: "<string.h>".}
proc getEnvVarsC() =
if not envComputed:
environment = @[]
when useWinUnicode:
var
env = getEnvironmentStringsW()
e = env
if e == nil: return # an error occurred
while true:
var eend = strEnd(e)
add(environment, $e)
e = cast[WideCString](cast[ByteAddress](eend)+2)
if eend[1].int == 0: break
discard freeEnvironmentStringsW(env)
else:
var
env = getEnvironmentStringsA()
e = env
if e == nil: return # an error occurred
while true:
var eend = strEnd(e)
add(environment, $e)
e = cast[cstring](cast[ByteAddress](eend)+1)
if eend[1] == '\0': break
discard freeEnvironmentStringsA(env)
envComputed = true
else:
const
useNSGetEnviron = defined(macosx) and not defined(ios)
when useNSGetEnviron:
# From the manual:
# Shared libraries and bundles don't have direct access to environ,
# which is only available to the loader ld(1) when a complete program
# is being linked.
# The environment routines can still be used, but if direct access to
# environ is needed, the _NSGetEnviron() routine, defined in
# <crt_externs.h>, can be used to retrieve the address of environ
# at runtime.
proc NSGetEnviron(): ptr cstringArray {.
importc: "_NSGetEnviron", header: "<crt_externs.h>".}
else:
var gEnv {.importc: "environ".}: cstringArray
proc getEnvVarsC() =
# retrieves the variables of char** env of C's main proc
if not envComputed:
environment = @[]
when useNSGetEnviron:
var gEnv = NSGetEnviron()[]
var i = 0
while true:
if gEnv[i] == nil: break
add environment, $gEnv[i]
inc(i)
envComputed = true
proc findEnvVar(key: string): int =
getEnvVarsC()
var temp = key & '='
for i in 0..high(environment):
if startsWith(environment[i], temp): return i
return -1
proc getEnv*(key: string): TaintedString {.tags: [ReadEnvEffect].} =
## Returns the value of the `environment variable`:idx: named `key`.
##
## If the variable does not exist, "" is returned. To distinguish
## whether a variable exists or it's value is just "", call
## `existsEnv(key)`.
when nimvm:
discard "built into the compiler"
else:
var i = findEnvVar(key)
if i >= 0:
return TaintedString(substr(environment[i], find(environment[i], '=')+1))
else:
var env = c_getenv(key)
if env == nil: return TaintedString("")
result = TaintedString($env)
proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} =
## Checks whether the environment variable named `key` exists.
## Returns true if it exists, false otherwise.
when nimvm:
discard "built into the compiler"
else:
if c_getenv(key) != nil: return true
else: return findEnvVar(key) >= 0
proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} =
## Sets the value of the `environment variable`:idx: named `key` to `val`.
## If an error occurs, `EInvalidEnvVar` is raised.
# Note: by storing the string in the environment sequence,
# we guarantee that we don't free the memory before the program
# ends (this is needed for POSIX compliance). It is also needed so that
# the process itself may access its modified environment variables!
when nimvm:
discard "built into the compiler"
else:
var indx = findEnvVar(key)
if indx >= 0:
environment[indx] = key & '=' & val
else:
add environment, (key & '=' & val)
indx = high(environment)
when defined(windows):
when useWinUnicode:
var k = newWideCString(key)
var v = newWideCString(val)
if setEnvironmentVariableW(k, v) == 0'i32: raiseOSError(osLastError())
else:
if setEnvironmentVariableA(key, val) == 0'i32: raiseOSError(osLastError())
else:
if c_putenv(environment[indx]) != 0'i32:
raiseOSError(osLastError())
iterator envPairs*(): tuple[key, value: TaintedString] {.tags: [ReadEnvEffect].} =
## Iterate over all `environments variables`:idx:. In the first component
## of the tuple is the name of the current variable stored, in the second
## its value.
getEnvVarsC()
for i in 0..high(environment):
var p = find(environment[i], '=')
yield (TaintedString(substr(environment[i], 0, p-1)),
TaintedString(substr(environment[i], p+1)))

132
lib/pure/includes/oserr.nim Normal file
View file

@ -0,0 +1,132 @@
## Include file that implements 'osErrorMsg' and friends. Do not import it!
when not declared(ospaths):
{.error: "This is an include file for ospaths.nim!".}
when not defined(nimscript):
var errno {.importc, header: "<errno.h>".}: cint
proc c_strerror(errnum: cint): cstring {.
importc: "strerror", header: "<string.h>".}
proc osErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} =
## Retrieves the operating system's error flag, ``errno``.
## On Windows ``GetLastError`` is checked before ``errno``.
## Returns "" if no error occurred.
##
## **Deprecated since version 0.9.4**: use the other ``osErrorMsg`` proc.
result = ""
when defined(Windows) and not defined(nimscript):
var err = getLastError()
if err != 0'i32:
when useWinUnicode:
var msgbuf: WideCString
if formatMessageW(0x00000100 or 0x00001000 or 0x00000200 or 0x000000FF,
nil, err, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: localFree(cast[pointer](msgbuf))
else:
var msgbuf: cstring
if formatMessageA(0x00000100 or 0x00001000 or 0x00000200 or 0x000000FF,
nil, err, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: localFree(msgbuf)
when not defined(nimscript):
if errno != 0'i32:
result = $c_strerror(errno)
{.push warning[deprecated]: off.}
proc raiseOSError*(msg: string = "") {.noinline, rtl, extern: "nos$1",
deprecated.} =
## raises an OSError exception with the given message ``msg``.
## If ``msg == ""``, the operating system's error flag
## (``errno``) is converted to a readable error message. On Windows
## ``GetLastError`` is checked before ``errno``.
## If no error flag is set, the message ``unknown OS error`` is used.
##
## **Deprecated since version 0.9.4**: use the other ``raiseOSError`` proc.
if len(msg) == 0:
var m = osErrorMsg()
raise newException(OSError, if m.len > 0: m else: "unknown OS error")
else:
raise newException(OSError, msg)
{.pop.}
when not defined(nimfix):
{.deprecated: [osError: raiseOSError].}
proc `==`*(err1, err2: OSErrorCode): bool {.borrow.}
proc `$`*(err: OSErrorCode): string {.borrow.}
proc osErrorMsg*(errorCode: OSErrorCode): string =
## Converts an OS error code into a human readable string.
##
## The error code can be retrieved using the ``osLastError`` proc.
##
## If conversion fails, or ``errorCode`` is ``0`` then ``""`` will be
## returned.
##
## On Windows, the ``-d:useWinAnsi`` compilation flag can be used to
## make this procedure use the non-unicode Win API calls to retrieve the
## message.
result = ""
when defined(nimscript):
discard
elif defined(Windows):
if errorCode != OSErrorCode(0'i32):
when useWinUnicode:
var msgbuf: WideCString
if formatMessageW(0x00000100 or 0x00001000 or 0x00000200,
nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: localFree(cast[pointer](msgbuf))
else:
var msgbuf: cstring
if formatMessageA(0x00000100 or 0x00001000 or 0x00000200,
nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: localFree(msgbuf)
else:
if errorCode != OSErrorCode(0'i32):
result = $c_strerror(errorCode.int32)
proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} =
## Raises an ``OSError`` exception. The ``errorCode`` will determine the
## message, ``osErrorMsg`` will be used to get this message.
##
## The error code can be retrieved using the ``osLastError`` proc.
##
## If the error code is ``0`` or an error message could not be retrieved,
## the message ``unknown OS error`` will be used.
var e: ref OSError; new(e)
e.errorCode = errorCode.int32
if additionalInfo.len == 0:
e.msg = osErrorMsg(errorCode)
else:
e.msg = osErrorMsg(errorCode) & "\nAdditional info: " & additionalInfo
if e.msg == "":
e.msg = "unknown OS error"
raise e
{.push stackTrace:off.}
proc osLastError*(): OSErrorCode =
## Retrieves the last operating system error code.
##
## This procedure is useful in the event when an OS call fails. In that case
## this procedure will return the error code describing the reason why the
## OS call failed. The ``OSErrorMsg`` procedure can then be used to convert
## this code into a string.
##
## **Warning**:
## The behaviour of this procedure varies between Windows and POSIX systems.
## On Windows some OS calls can reset the error code to ``0`` causing this
## procedure to return ``0``. It is therefore advised to call this procedure
## immediately after an OS call fails. On POSIX systems this is not a problem.
when defined(nimscript):
discard
elif defined(windows):
result = OSErrorCode(getLastError())
else:
result = OSErrorCode(errno)
{.pop.}

View file

@ -35,136 +35,11 @@ proc c_rename(oldname, newname: cstring): cint {.
importc: "rename", header: "<stdio.h>".} importc: "rename", header: "<stdio.h>".}
proc c_system(cmd: cstring): cint {. proc c_system(cmd: cstring): cint {.
importc: "system", header: "<stdlib.h>".} importc: "system", header: "<stdlib.h>".}
proc c_strerror(errnum: cint): cstring {.
importc: "strerror", header: "<string.h>".}
proc c_strlen(a: cstring): cint {. proc c_strlen(a: cstring): cint {.
importc: "strlen", header: "<string.h>", noSideEffect.} importc: "strlen", header: "<string.h>", noSideEffect.}
proc c_getenv(env: cstring): cstring {.
importc: "getenv", header: "<stdlib.h>".}
proc c_putenv(env: cstring): cint {.
importc: "putenv", header: "<stdlib.h>".}
proc c_free(p: pointer) {. proc c_free(p: pointer) {.
importc: "free", header: "<stdlib.h>".} importc: "free", header: "<stdlib.h>".}
var errno {.importc, header: "<errno.h>".}: cint
proc osErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} =
## Retrieves the operating system's error flag, ``errno``.
## On Windows ``GetLastError`` is checked before ``errno``.
## Returns "" if no error occurred.
##
## **Deprecated since version 0.9.4**: use the other ``osErrorMsg`` proc.
result = ""
when defined(Windows):
var err = getLastError()
if err != 0'i32:
when useWinUnicode:
var msgbuf: WideCString
if formatMessageW(0x00000100 or 0x00001000 or 0x00000200 or 0x000000FF,
nil, err, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: localFree(cast[pointer](msgbuf))
else:
var msgbuf: cstring
if formatMessageA(0x00000100 or 0x00001000 or 0x00000200 or 0x000000FF,
nil, err, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: localFree(msgbuf)
if errno != 0'i32:
result = $os.c_strerror(errno)
{.push warning[deprecated]: off.}
proc raiseOSError*(msg: string = "") {.noinline, rtl, extern: "nos$1",
deprecated.} =
## raises an OSError exception with the given message ``msg``.
## If ``msg == ""``, the operating system's error flag
## (``errno``) is converted to a readable error message. On Windows
## ``GetLastError`` is checked before ``errno``.
## If no error flag is set, the message ``unknown OS error`` is used.
##
## **Deprecated since version 0.9.4**: use the other ``raiseOSError`` proc.
if len(msg) == 0:
var m = osErrorMsg()
raise newException(OSError, if m.len > 0: m else: "unknown OS error")
else:
raise newException(OSError, msg)
{.pop.}
when not defined(nimfix):
{.deprecated: [osError: raiseOSError].}
proc `==`*(err1, err2: OSErrorCode): bool {.borrow.}
proc `$`*(err: OSErrorCode): string {.borrow.}
proc osErrorMsg*(errorCode: OSErrorCode): string =
## Converts an OS error code into a human readable string.
##
## The error code can be retrieved using the ``osLastError`` proc.
##
## If conversion fails, or ``errorCode`` is ``0`` then ``""`` will be
## returned.
##
## On Windows, the ``-d:useWinAnsi`` compilation flag can be used to
## make this procedure use the non-unicode Win API calls to retrieve the
## message.
result = ""
when defined(Windows):
if errorCode != OSErrorCode(0'i32):
when useWinUnicode:
var msgbuf: WideCString
if formatMessageW(0x00000100 or 0x00001000 or 0x00000200,
nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: localFree(cast[pointer](msgbuf))
else:
var msgbuf: cstring
if formatMessageA(0x00000100 or 0x00001000 or 0x00000200,
nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: localFree(msgbuf)
else:
if errorCode != OSErrorCode(0'i32):
result = $os.c_strerror(errorCode.int32)
proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} =
## Raises an ``OSError`` exception. The ``errorCode`` will determine the
## message, ``osErrorMsg`` will be used to get this message.
##
## The error code can be retrieved using the ``osLastError`` proc.
##
## If the error code is ``0`` or an error message could not be retrieved,
## the message ``unknown OS error`` will be used.
var e: ref OSError; new(e)
e.errorCode = errorCode.int32
if additionalInfo.len == 0:
e.msg = osErrorMsg(errorCode)
else:
e.msg = osErrorMsg(errorCode) & "\nAdditional info: " & additionalInfo
if e.msg == "":
e.msg = "unknown OS error"
raise e
{.push stackTrace:off.}
proc osLastError*(): OSErrorCode =
## Retrieves the last operating system error code.
##
## This procedure is useful in the event when an OS call fails. In that case
## this procedure will return the error code describing the reason why the
## OS call failed. The ``OSErrorMsg`` procedure can then be used to convert
## this code into a string.
##
## **Warning**:
## The behaviour of this procedure varies between Windows and POSIX systems.
## On Windows some OS calls can reset the error code to ``0`` causing this
## procedure to return ``0``. It is therefore advised to call this procedure
## immediately after an OS call fails. On POSIX systems this is not a problem.
when defined(windows):
result = OSErrorCode(getLastError())
else:
result = OSErrorCode(errno)
{.pop.}
when defined(windows): when defined(windows):
when useWinUnicode: when useWinUnicode:
@ -244,6 +119,60 @@ proc dirExists*(dir: string): bool {.inline.} =
## Synonym for existsDir ## Synonym for existsDir
existsDir(dir) existsDir(dir)
when not defined(windows):
proc checkSymlink(path: string): bool =
var rawInfo: Stat
if lstat(path, rawInfo) < 0'i32: result = false
else: result = S_ISLNK(rawInfo.st_mode)
const
ExeExts* = when defined(windows): ["exe", "cmd", "bat"] else: [""] ## \
## platform specific file extension for executables. On Windows
## ``["exe", "cmd", "bat"]``, on Posix ``[""]``.
proc findExe*(exe: string, followSymlinks: bool = true;
extensions: openarray[string]=ExeExts): string {.
tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect].} =
## Searches for `exe` in the current working directory and then
## in directories listed in the ``PATH`` environment variable.
## Returns "" if the `exe` cannot be found. `exe`
## is added the `ExeExts <#ExeExts>`_ file extensions if it has none.
## If the system supports symlinks it also resolves them until it
## meets the actual file. This behavior can be disabled if desired.
for ext in extensions:
result = addFileExt(exe, ext)
if existsFile(result): return
var path = string(getEnv("PATH"))
for candidate in split(path, PathSep):
when defined(windows):
var x = (if candidate[0] == '"' and candidate[^1] == '"':
substr(candidate, 1, candidate.len-2) else: candidate) /
exe
else:
var x = expandTilde(candidate) / exe
for ext in extensions:
var x = addFileExt(x, ext)
if existsFile(x):
when not defined(windows):
while followSymlinks: # doubles as if here
if x.checkSymlink:
var r = newString(256)
var len = readlink(x, r, 256)
if len < 0:
raiseOSError(osLastError())
if len > 256:
r = newString(len+1)
len = readlink(x, r, len)
setLen(r, len)
if isAbsolute(r):
x = r
else:
x = parentDir(x) / r
else:
break
return x
result = ""
proc getLastModificationTime*(file: string): Time {.rtl, extern: "nos$1".} = proc getLastModificationTime*(file: string): Time {.rtl, extern: "nos$1".} =
## Returns the `file`'s last modification time. ## Returns the `file`'s last modification time.
when defined(posix): when defined(posix):
@ -706,147 +635,6 @@ proc execShellCmd*(command: string): int {.rtl, extern: "nos$1",
else: else:
result = c_system(command) result = c_system(command)
# Environment handling cannot be put into RTL, because the ``envPairs``
# iterator depends on ``environment``.
var
envComputed {.threadvar.}: bool
environment {.threadvar.}: seq[string]
when defined(windows):
# because we support Windows GUI applications, things get really
# messy here...
when useWinUnicode:
when defined(cpp):
proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.
importcpp: "(NI16*)wcschr((const wchar_t *)#, #)", header: "<string.h>".}
else:
proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.
importc: "wcschr", header: "<string.h>".}
else:
proc strEnd(cstr: cstring, c = 0'i32): cstring {.
importc: "strchr", header: "<string.h>".}
proc getEnvVarsC() =
if not envComputed:
environment = @[]
when useWinUnicode:
var
env = getEnvironmentStringsW()
e = env
if e == nil: return # an error occurred
while true:
var eend = strEnd(e)
add(environment, $e)
e = cast[WideCString](cast[ByteAddress](eend)+2)
if eend[1].int == 0: break
discard freeEnvironmentStringsW(env)
else:
var
env = getEnvironmentStringsA()
e = env
if e == nil: return # an error occurred
while true:
var eend = strEnd(e)
add(environment, $e)
e = cast[cstring](cast[ByteAddress](eend)+1)
if eend[1] == '\0': break
discard freeEnvironmentStringsA(env)
envComputed = true
else:
const
useNSGetEnviron = defined(macosx) and not defined(ios)
when useNSGetEnviron:
# From the manual:
# Shared libraries and bundles don't have direct access to environ,
# which is only available to the loader ld(1) when a complete program
# is being linked.
# The environment routines can still be used, but if direct access to
# environ is needed, the _NSGetEnviron() routine, defined in
# <crt_externs.h>, can be used to retrieve the address of environ
# at runtime.
proc NSGetEnviron(): ptr cstringArray {.
importc: "_NSGetEnviron", header: "<crt_externs.h>".}
else:
var gEnv {.importc: "environ".}: cstringArray
proc getEnvVarsC() =
# retrieves the variables of char** env of C's main proc
if not envComputed:
environment = @[]
when useNSGetEnviron:
var gEnv = NSGetEnviron()[]
var i = 0
while true:
if gEnv[i] == nil: break
add environment, $gEnv[i]
inc(i)
envComputed = true
proc findEnvVar(key: string): int =
getEnvVarsC()
var temp = key & '='
for i in 0..high(environment):
if startsWith(environment[i], temp): return i
return -1
proc getEnv*(key: string): TaintedString {.tags: [ReadEnvEffect].} =
## Returns the value of the `environment variable`:idx: named `key`.
##
## If the variable does not exist, "" is returned. To distinguish
## whether a variable exists or it's value is just "", call
## `existsEnv(key)`.
var i = findEnvVar(key)
if i >= 0:
return TaintedString(substr(environment[i], find(environment[i], '=')+1))
else:
var env = c_getenv(key)
if env == nil: return TaintedString("")
result = TaintedString($env)
proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} =
## Checks whether the environment variable named `key` exists.
## Returns true if it exists, false otherwise.
if c_getenv(key) != nil: return true
else: return findEnvVar(key) >= 0
proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} =
## Sets the value of the `environment variable`:idx: named `key` to `val`.
## If an error occurs, `EInvalidEnvVar` is raised.
# Note: by storing the string in the environment sequence,
# we guarantee that we don't free the memory before the program
# ends (this is needed for POSIX compliance). It is also needed so that
# the process itself may access its modified environment variables!
var indx = findEnvVar(key)
if indx >= 0:
environment[indx] = key & '=' & val
else:
add environment, (key & '=' & val)
indx = high(environment)
when defined(windows):
when useWinUnicode:
var k = newWideCString(key)
var v = newWideCString(val)
if setEnvironmentVariableW(k, v) == 0'i32: raiseOSError(osLastError())
else:
if setEnvironmentVariableA(key, val) == 0'i32: raiseOSError(osLastError())
else:
if c_putenv(environment[indx]) != 0'i32:
raiseOSError(osLastError())
iterator envPairs*(): tuple[key, value: TaintedString] {.tags: [ReadEnvEffect].} =
## Iterate over all `environments variables`:idx:. In the first component
## of the tuple is the name of the current variable stored, in the second
## its value.
getEnvVarsC()
for i in 0..high(environment):
var p = find(environment[i], '=')
yield (TaintedString(substr(environment[i], 0, p-1)),
TaintedString(substr(environment[i], p+1)))
# Templates for filtering directories and files # Templates for filtering directories and files
when defined(windows): when defined(windows):
template isDir(f: WIN32_FIND_DATA): bool = template isDir(f: WIN32_FIND_DATA): bool =

View file

@ -7,19 +7,14 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
# Included by the ``os`` module but a module in its own right for NimScript # Forwarded by the ``os`` module but a module in its own right for NimScript
# support. # support.
when not declared(os): include "system/inclrtl"
{.pragma: rtl.}
import strutils
when defined(nimscript) or (defined(nimdoc) and not declared(os)): import strutils
{.pragma: rtl.}
{.push hint[ConvFromXtoItselfNotNeeded]:off.}
when not declared(getEnv) or defined(nimscript): type
type
ReadEnvEffect* = object of ReadIOEffect ## effect that denotes a read ReadEnvEffect* = object of ReadIOEffect ## effect that denotes a read
## from an environment variable ## from an environment variable
WriteEnvEffect* = object of WriteIOEffect ## effect that denotes a write WriteEnvEffect* = object of WriteIOEffect ## effect that denotes a write
@ -34,15 +29,15 @@ when not declared(getEnv) or defined(nimscript):
OSErrorCode* = distinct int32 ## Specifies an OS Error Code. OSErrorCode* = distinct int32 ## Specifies an OS Error Code.
{.deprecated: [FReadEnv: ReadEnvEffect, FWriteEnv: WriteEnvEffect, {.deprecated: [FReadEnv: ReadEnvEffect, FWriteEnv: WriteEnvEffect,
FReadDir: ReadDirEffect, FReadDir: ReadDirEffect,
FWriteDir: WriteDirEffect, FWriteDir: WriteDirEffect,
TOSErrorCode: OSErrorCode TOSErrorCode: OSErrorCode
].} ].}
const const
doslikeFileSystem* = defined(windows) or defined(OS2) or defined(DOS) doslikeFileSystem* = defined(windows) or defined(OS2) or defined(DOS)
when defined(Nimdoc): # only for proper documentation: when defined(Nimdoc): # only for proper documentation:
const const
CurDir* = '.' CurDir* = '.'
## The constant string used by the operating system to refer to the ## The constant string used by the operating system to refer to the
@ -88,7 +83,7 @@ when not declared(getEnv) or defined(nimscript):
## The format string to turn a filename into a `DLL`:idx: file (also ## The format string to turn a filename into a `DLL`:idx: file (also
## called `shared object`:idx: on some operating systems). ## called `shared object`:idx: on some operating systems).
elif defined(macos): elif defined(macos):
const const
CurDir* = ':' CurDir* = ':'
ParDir* = "::" ParDir* = "::"
@ -119,7 +114,7 @@ when not declared(getEnv) or defined(nimscript):
# waterproof. In case of equal names the first volume found will do. # waterproof. In case of equal names the first volume found will do.
# Two colons "::" are the relative path to the parent. Three is to the # Two colons "::" are the relative path to the parent. Three is to the
# grandparent etc. # grandparent etc.
elif doslikeFileSystem: elif doslikeFileSystem:
const const
CurDir* = '.' CurDir* = '.'
ParDir* = ".." ParDir* = ".."
@ -130,7 +125,7 @@ when not declared(getEnv) or defined(nimscript):
ExeExt* = "exe" ExeExt* = "exe"
ScriptExt* = "bat" ScriptExt* = "bat"
DynlibFormat* = "$1.dll" DynlibFormat* = "$1.dll"
elif defined(PalmOS) or defined(MorphOS): elif defined(PalmOS) or defined(MorphOS):
const const
DirSep* = '/' DirSep* = '/'
AltSep* = Dirsep AltSep* = Dirsep
@ -140,7 +135,7 @@ when not declared(getEnv) or defined(nimscript):
ExeExt* = "" ExeExt* = ""
ScriptExt* = "" ScriptExt* = ""
DynlibFormat* = "$1.prc" DynlibFormat* = "$1.prc"
elif defined(RISCOS): elif defined(RISCOS):
const const
DirSep* = '.' DirSep* = '.'
AltSep* = '.' AltSep* = '.'
@ -150,7 +145,7 @@ when not declared(getEnv) or defined(nimscript):
ExeExt* = "" ExeExt* = ""
ScriptExt* = "" ScriptExt* = ""
DynlibFormat* = "lib$1.so" DynlibFormat* = "lib$1.so"
else: # UNIX-like operating system else: # UNIX-like operating system
const const
CurDir* = '.' CurDir* = '.'
ParDir* = ".." ParDir* = ".."
@ -162,13 +157,13 @@ when not declared(getEnv) or defined(nimscript):
ScriptExt* = "" ScriptExt* = ""
DynlibFormat* = when defined(macosx): "lib$1.dylib" else: "lib$1.so" DynlibFormat* = when defined(macosx): "lib$1.dylib" else: "lib$1.so"
const const
ExtSep* = '.' ExtSep* = '.'
## The character which separates the base filename from the extension; ## The character which separates the base filename from the extension;
## for example, the '.' in ``os.nim``. ## for example, the '.' in ``os.nim``.
proc joinPath*(head, tail: string): string {. proc joinPath*(head, tail: string): string {.
noSideEffect, rtl, extern: "nos$1".} = noSideEffect, rtl, extern: "nos$1".} =
## Joins two directory names to one. ## Joins two directory names to one.
## ##
@ -206,7 +201,7 @@ when not declared(getEnv) or defined(nimscript):
else: else:
result = head & DirSep & tail result = head & DirSep & tail
proc joinPath*(parts: varargs[string]): string {.noSideEffect, proc joinPath*(parts: varargs[string]): string {.noSideEffect,
rtl, extern: "nos$1OpenArray".} = rtl, extern: "nos$1OpenArray".} =
## The same as `joinPath(head, tail)`, but works with any number of ## The same as `joinPath(head, tail)`, but works with any number of
## directory parts. You need to pass at least one element or the proc ## directory parts. You need to pass at least one element or the proc
@ -215,7 +210,7 @@ when not declared(getEnv) or defined(nimscript):
for i in 1..high(parts): for i in 1..high(parts):
result = joinPath(result, parts[i]) result = joinPath(result, parts[i])
proc `/` * (head, tail: string): string {.noSideEffect.} = proc `/` * (head, tail: string): string {.noSideEffect.} =
## The same as ``joinPath(head, tail)`` ## The same as ``joinPath(head, tail)``
## ##
## Here are some examples for Unix: ## Here are some examples for Unix:
@ -227,7 +222,7 @@ when not declared(getEnv) or defined(nimscript):
## assert "usr/" / "/lib" == "usr/lib" ## assert "usr/" / "/lib" == "usr/lib"
return joinPath(head, tail) return joinPath(head, tail)
proc splitPath*(path: string): tuple[head, tail: string] {. proc splitPath*(path: string): tuple[head, tail: string] {.
noSideEffect, rtl, extern: "nos$1".} = noSideEffect, rtl, extern: "nos$1".} =
## Splits a directory into (head, tail), so that ## Splits a directory into (head, tail), so that
## ``head / tail == path`` (except for edge cases like "/usr"). ## ``head / tail == path`` (except for edge cases like "/usr").
@ -252,14 +247,14 @@ when not declared(getEnv) or defined(nimscript):
result.head = "" result.head = ""
result.tail = path result.tail = path
proc parentDirPos(path: string): int = proc parentDirPos(path: string): int =
var q = 1 var q = 1
if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2 if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2
for i in countdown(len(path)-q, 0): for i in countdown(len(path)-q, 0):
if path[i] in {DirSep, AltSep}: return i if path[i] in {DirSep, AltSep}: return i
result = -1 result = -1
proc parentDir*(path: string): string {. proc parentDir*(path: string): string {.
noSideEffect, rtl, extern: "nos$1".} = noSideEffect, rtl, extern: "nos$1".} =
## Returns the parent directory of `path`. ## Returns the parent directory of `path`.
## ##
@ -273,7 +268,7 @@ when not declared(getEnv) or defined(nimscript):
else: else:
result = "" result = ""
proc tailDir*(path: string): string {. proc tailDir*(path: string): string {.
noSideEffect, rtl, extern: "nos$1".} = noSideEffect, rtl, extern: "nos$1".} =
## Returns the tail part of `path`.. ## Returns the tail part of `path`..
## ##
@ -287,12 +282,12 @@ when not declared(getEnv) or defined(nimscript):
return substr(path, i+1) return substr(path, i+1)
result = "" result = ""
proc isRootDir*(path: string): bool {. proc isRootDir*(path: string): bool {.
noSideEffect, rtl, extern: "nos$1".} = noSideEffect, rtl, extern: "nos$1".} =
## Checks whether a given `path` is a root directory ## Checks whether a given `path` is a root directory
result = parentDirPos(path) < 0 result = parentDirPos(path) < 0
iterator parentDirs*(path: string, fromRoot=false, inclusive=true): string = iterator parentDirs*(path: string, fromRoot=false, inclusive=true): string =
## Walks over all parent directories of a given `path` ## Walks over all parent directories of a given `path`
## ##
## If `fromRoot` is set, the traversal will start from the file system root ## If `fromRoot` is set, the traversal will start from the file system root
@ -317,7 +312,7 @@ when not declared(getEnv) or defined(nimscript):
if inclusive: yield path if inclusive: yield path
proc `/../` * (head, tail: string): string {.noSideEffect.} = proc `/../`*(head, tail: string): string {.noSideEffect.} =
## The same as ``parentDir(head) / tail`` unless there is no parent ## The same as ``parentDir(head) / tail`` unless there is no parent
## directory. Then ``head / tail`` is performed instead. ## directory. Then ``head / tail`` is performed instead.
let sepPos = parentDirPos(head) let sepPos = parentDirPos(head)
@ -326,11 +321,11 @@ when not declared(getEnv) or defined(nimscript):
else: else:
result = head / tail result = head / tail
proc normExt(ext: string): string = proc normExt(ext: string): string =
if ext == "" or ext[0] == ExtSep: result = ext # no copy needed here if ext == "" or ext[0] == ExtSep: result = ext # no copy needed here
else: result = ExtSep & ext else: result = ExtSep & ext
proc searchExtPos*(path: string): int = proc searchExtPos*(path: string): int =
## Returns index of the '.' char in `path` if it signifies the beginning ## Returns index of the '.' char in `path` if it signifies the beginning
## of extension. Returns -1 otherwise. ## of extension. Returns -1 otherwise.
# BUGFIX: do not search until 0! .DS_Store is no file extension! # BUGFIX: do not search until 0! .DS_Store is no file extension!
@ -342,7 +337,7 @@ when not declared(getEnv) or defined(nimscript):
elif path[i] in {DirSep, AltSep}: elif path[i] in {DirSep, AltSep}:
break # do not skip over path break # do not skip over path
proc splitFile*(path: string): tuple[dir, name, ext: string] {. proc splitFile*(path: string): tuple[dir, name, ext: string] {.
noSideEffect, rtl, extern: "nos$1".} = noSideEffect, rtl, extern: "nos$1".} =
## Splits a filename into (dir, filename, extension). ## Splits a filename into (dir, filename, extension).
## `dir` does not end in `DirSep`. ## `dir` does not end in `DirSep`.
@ -375,7 +370,7 @@ when not declared(getEnv) or defined(nimscript):
result.name = substr(path, sepPos+1, dotPos-1) result.name = substr(path, sepPos+1, dotPos-1)
result.ext = substr(path, dotPos) result.ext = substr(path, dotPos)
proc extractFilename*(path: string): string {. proc extractFilename*(path: string): string {.
noSideEffect, rtl, extern: "nos$1".} = noSideEffect, rtl, extern: "nos$1".} =
## Extracts the filename of a given `path`. This is the same as ## Extracts the filename of a given `path`. This is the same as
## ``name & ext`` from ``splitFile(path)``. ## ``name & ext`` from ``splitFile(path)``.
@ -385,7 +380,7 @@ when not declared(getEnv) or defined(nimscript):
result = splitPath(path).tail result = splitPath(path).tail
proc changeFileExt*(filename, ext: string): string {. proc changeFileExt*(filename, ext: string): string {.
noSideEffect, rtl, extern: "nos$1".} = noSideEffect, rtl, extern: "nos$1".} =
## Changes the file extension to `ext`. ## Changes the file extension to `ext`.
## ##
@ -398,7 +393,7 @@ when not declared(getEnv) or defined(nimscript):
if extPos < 0: result = filename & normExt(ext) if extPos < 0: result = filename & normExt(ext)
else: result = substr(filename, 0, extPos-1) & normExt(ext) else: result = substr(filename, 0, extPos-1) & normExt(ext)
proc addFileExt*(filename, ext: string): string {. proc addFileExt*(filename, ext: string): string {.
noSideEffect, rtl, extern: "nos$1".} = noSideEffect, rtl, extern: "nos$1".} =
## Adds the file extension `ext` to `filename`, unless ## Adds the file extension `ext` to `filename`, unless
## `filename` already has an extension. ## `filename` already has an extension.
@ -410,7 +405,7 @@ when not declared(getEnv) or defined(nimscript):
if extPos < 0: result = filename & normExt(ext) if extPos < 0: result = filename & normExt(ext)
else: result = filename else: result = filename
proc cmpPaths*(pathA, pathB: string): int {. proc cmpPaths*(pathA, pathB: string): int {.
noSideEffect, rtl, extern: "nos$1".} = noSideEffect, rtl, extern: "nos$1".} =
## Compares two paths. ## Compares two paths.
## ##
@ -429,7 +424,7 @@ when not declared(getEnv) or defined(nimscript):
else: else:
result = cmpIgnoreCase(pathA, pathB) result = cmpIgnoreCase(pathA, pathB)
proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} = proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} =
## Checks whether a given `path` is absolute. ## Checks whether a given `path` is absolute.
## ##
## On Windows, network paths are considered absolute too. ## On Windows, network paths are considered absolute too.
@ -444,7 +439,7 @@ when not declared(getEnv) or defined(nimscript):
elif defined(posix): elif defined(posix):
result = path[0] == '/' result = path[0] == '/'
proc unixToNativePath*(path: string, drive=""): string {. proc unixToNativePath*(path: string, drive=""): string {.
noSideEffect, rtl, extern: "nos$1".} = noSideEffect, rtl, extern: "nos$1".} =
## Converts an UNIX-like path to a native one. ## Converts an UNIX-like path to a native one.
## ##
@ -499,12 +494,10 @@ when not declared(getEnv) or defined(nimscript):
add result, path[i] add result, path[i]
inc(i) inc(i)
when defined(nimdoc) and not declared(os): include "includes/oserr"
proc getEnv(x: string): string = discard include "includes/osenv"
proc existsFile(x: string): bool = discard
when declared(getEnv) or defined(nimscript): proc getHomeDir*(): string {.rtl, extern: "nos$1",
proc getHomeDir*(): string {.rtl, extern: "nos$1",
tags: [ReadEnvEffect, ReadIOEffect].} = tags: [ReadEnvEffect, ReadIOEffect].} =
## Returns the home directory of the current user. ## Returns the home directory of the current user.
## ##
@ -513,7 +506,7 @@ when declared(getEnv) or defined(nimscript):
when defined(windows): return string(getEnv("USERPROFILE")) & "\\" when defined(windows): return string(getEnv("USERPROFILE")) & "\\"
else: return string(getEnv("HOME")) & "/" else: return string(getEnv("HOME")) & "/"
proc getConfigDir*(): string {.rtl, extern: "nos$1", proc getConfigDir*(): string {.rtl, extern: "nos$1",
tags: [ReadEnvEffect, ReadIOEffect].} = tags: [ReadEnvEffect, ReadIOEffect].} =
## Returns the config directory of the current user for applications. ## Returns the config directory of the current user for applications.
## ##
@ -528,7 +521,7 @@ when declared(getEnv) or defined(nimscript):
elif getEnv("XDG_CONFIG_DIR"): return string(getEnv("XDG_CONFIG_DIR")) & "/" elif getEnv("XDG_CONFIG_DIR"): return string(getEnv("XDG_CONFIG_DIR")) & "/"
else: return string(getEnv("HOME")) & "/.config/" else: return string(getEnv("HOME")) & "/.config/"
proc getTempDir*(): string {.rtl, extern: "nos$1", proc getTempDir*(): string {.rtl, extern: "nos$1",
tags: [ReadEnvEffect, ReadIOEffect].} = tags: [ReadEnvEffect, ReadIOEffect].} =
## Returns the temporary directory of the current user for applications to ## Returns the temporary directory of the current user for applications to
## save temporary files in. ## save temporary files in.
@ -544,7 +537,7 @@ when declared(getEnv) or defined(nimscript):
elif defined(android): return getHomeDir() elif defined(android): return getHomeDir()
else: return "/tmp/" else: return "/tmp/"
proc expandTilde*(path: string): string {. proc expandTilde*(path: string): string {.
tags: [ReadEnvEffect, ReadIOEffect].} = tags: [ReadEnvEffect, ReadIOEffect].} =
## Expands a path starting with ``~/`` to a full path. ## Expands a path starting with ``~/`` to a full path.
## ##
@ -564,70 +557,3 @@ when declared(getEnv) or defined(nimscript):
result = getHomeDir() / path.substr(2) result = getHomeDir() / path.substr(2)
else: else:
result = path result = path
when not declared(split):
iterator split(s: string, sep: char): string =
var last = 0
if len(s) > 0:
while last <= len(s):
var first = last
while last < len(s) and s[last] != sep: inc(last)
yield substr(s, first, last-1)
inc(last)
when not defined(windows) and declared(os):
proc checkSymlink(path: string): bool =
var rawInfo: Stat
if lstat(path, rawInfo) < 0'i32: result = false
else: result = S_ISLNK(rawInfo.st_mode)
const
ExeExts* = when defined(windows): ["exe", "cmd", "bat"] else: [""] ## \
## platform specific file extension for executables. On Windows
## ``["exe", "cmd", "bat"]``, on Posix ``[""]``.
proc findExe*(exe: string, followSymlinks: bool = true;
extensions: openarray[string]=ExeExts): string {.
tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect].} =
## Searches for `exe` in the current working directory and then
## in directories listed in the ``PATH`` environment variable.
## Returns "" if the `exe` cannot be found. `exe`
## is added the `ExeExts <#ExeExts>`_ file extensions if it has none.
## If the system supports symlinks it also resolves them until it
## meets the actual file. This behavior can be disabled if desired.
for ext in extensions:
result = addFileExt(exe, ext)
if existsFile(result): return
var path = string(getEnv("PATH"))
for candidate in split(path, PathSep):
when defined(windows):
var x = (if candidate[0] == '"' and candidate[^1] == '"':
substr(candidate, 1, candidate.len-2) else: candidate) /
exe
else:
var x = expandTilde(candidate) / exe
for ext in extensions:
var x = addFileExt(x, ext)
if existsFile(x):
when not defined(windows) and declared(os):
while followSymlinks: # doubles as if here
if x.checkSymlink:
var r = newString(256)
var len = readlink(x, r, 256)
if len < 0:
raiseOSError(osLastError())
if len > 256:
r = newString(len+1)
len = readlink(x, r, len)
setLen(r, len)
if isAbsolute(r):
x = r
else:
x = parentDir(x) / r
else:
break
return x
result = ""
when defined(nimscript) or (defined(nimdoc) and not declared(os)):
{.pop.} # hint[ConvFromXtoItselfNotNeeded]:off