diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2018-11-09 16:36:49 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-11-09 16:36:49 +0100 |
commit | 8d850f7a69d473360c5b1a9db9fd998ab9b5dff4 (patch) | |
tree | 5debabc119da6ed297d915614d0ec028fc1f534b /lib/pure | |
parent | 8507e506cedc24dca91632092f3899602a262276 (diff) | |
download | Nim-8d850f7a69d473360c5b1a9db9fd998ab9b5dff4.tar.gz |
deprecated ospaths (#9665)
Diffstat (limited to 'lib/pure')
-rw-r--r-- | lib/pure/includes/osenv.nim | 4 | ||||
-rw-r--r-- | lib/pure/includes/oserr.nim | 4 | ||||
-rw-r--r-- | lib/pure/os.nim | 901 | ||||
-rw-r--r-- | lib/pure/ospaths.nim | 716 | ||||
-rw-r--r-- | lib/pure/osproc.nim | 2 |
5 files changed, 824 insertions, 803 deletions
diff --git a/lib/pure/includes/osenv.nim b/lib/pure/includes/osenv.nim index ae62a5c4e..757b0b737 100644 --- a/lib/pure/includes/osenv.nim +++ b/lib/pure/includes/osenv.nim @@ -1,7 +1,7 @@ ## Include file that implements 'getEnv' and friends. Do not import it! -when not declared(ospaths): - {.error: "This is an include file for ospaths.nim!".} +when not declared(os): + {.error: "This is an include file for os.nim!".} proc c_getenv(env: cstring): cstring {. importc: "getenv", header: "<stdlib.h>".} diff --git a/lib/pure/includes/oserr.nim b/lib/pure/includes/oserr.nim index 31212d0d1..72c3f4f49 100644 --- a/lib/pure/includes/oserr.nim +++ b/lib/pure/includes/oserr.nim @@ -1,7 +1,7 @@ ## 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 declared(os): + {.error: "This is an include file for os.nim!".} when not defined(nimscript): var errno {.importc, header: "<errno.h>".}: cint diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 9d74158fe..658bb6d3a 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -17,33 +17,701 @@ include "system/inclrtl" import - strutils, times + strutils -when defined(windows): - import winlean +when defined(nimscript): + discard +elif defined(windows): + import winlean, times elif defined(posix): - import posix + import posix, times proc toTime(ts: Timespec): times.Time {.inline.} = result = initTime(ts.tv_sec.int64, ts.tv_nsec.int) - else: {.error: "OS module not ported to your operating system!".} -import ospaths -export ospaths +when defined(nimscript) and defined(nimErrorProcCanHaveBody): + {.pragma: noNimScript, error: "this proc is not available on the NimScript target".} +else: + {.pragma: noNimScript.} + +type + ReadEnvEffect* = object of ReadIOEffect ## effect that denotes a read + ## from an environment variable + WriteEnvEffect* = object of WriteIOEffect ## effect that denotes a write + ## to an environment variable + + ReadDirEffect* = object of ReadIOEffect ## effect that denotes a read + ## operation from the directory + ## structure + WriteDirEffect* = object of WriteIOEffect ## effect that denotes a write + ## operation to + ## the directory structure + + OSErrorCode* = distinct int32 ## Specifies an OS Error Code. + +const + doslikeFileSystem* = defined(windows) or defined(OS2) or defined(DOS) + +when defined(Nimdoc): # only for proper documentation: + const + CurDir* = '.' + ## The constant string used by the operating system to refer to the + ## current directory. + ## + ## For example: '.' for POSIX or ':' for the classic Macintosh. + + ParDir* = ".." + ## The constant string used by the operating system to refer to the + ## parent directory. + ## + ## For example: ".." for POSIX or "::" for the classic Macintosh. + + DirSep* = '/' + ## The character used by the operating system to separate pathname + ## components, for example, '/' for POSIX or ':' for the classic + ## Macintosh. + + AltSep* = '/' + ## An alternative character used by the operating system to separate + ## pathname components, or the same as `DirSep` if only one separator + ## character exists. This is set to '/' on Windows systems + ## where `DirSep` is a backslash. + + PathSep* = ':' + ## The character conventionally used by the operating system to separate + ## search patch components (as in PATH), such as ':' for POSIX + ## or ';' for Windows. + + FileSystemCaseSensitive* = true + ## true if the file system is case sensitive, false otherwise. Used by + ## `cmpPaths` to compare filenames properly. + + ExeExt* = "" + ## The file extension of native executables. For example: + ## "" for POSIX, "exe" on Windows. + + ScriptExt* = "" + ## The file extension of a script file. For example: "" for POSIX, + ## "bat" on Windows. + + DynlibFormat* = "lib$1.so" + ## The format string to turn a filename into a `DLL`:idx: file (also + ## called `shared object`:idx: on some operating systems). + +elif defined(macos): + const + CurDir* = ':' + ParDir* = "::" + DirSep* = ':' + AltSep* = Dirsep + PathSep* = ',' + FileSystemCaseSensitive* = false + ExeExt* = "" + ScriptExt* = "" + DynlibFormat* = "$1.dylib" + + # MacOS paths + # =========== + # MacOS directory separator is a colon ":" which is the only character not + # allowed in filenames. + # + # A path containing no colon or which begins with a colon is a partial + # path. + # E.g. ":kalle:petter" ":kalle" "kalle" + # + # All other paths are full (absolute) paths. E.g. "HD:kalle:" "HD:" + # When generating paths, one is safe if one ensures that all partial paths + # begin with a colon, and all full paths end with a colon. + # In full paths the first name (e g HD above) is the name of a mounted + # volume. + # These names are not unique, because, for instance, two diskettes with the + # same names could be inserted. This means that paths on MacOS are not + # 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 + # grandparent etc. +elif doslikeFileSystem: + const + CurDir* = '.' + ParDir* = ".." + DirSep* = '\\' # seperator within paths + AltSep* = '/' + PathSep* = ';' # seperator between paths + FileSystemCaseSensitive* = false + ExeExt* = "exe" + ScriptExt* = "bat" + DynlibFormat* = "$1.dll" +elif defined(PalmOS) or defined(MorphOS): + const + DirSep* = '/' + AltSep* = Dirsep + PathSep* = ';' + ParDir* = ".." + FileSystemCaseSensitive* = false + ExeExt* = "" + ScriptExt* = "" + DynlibFormat* = "$1.prc" +elif defined(RISCOS): + const + DirSep* = '.' + AltSep* = '.' + ParDir* = ".." # is this correct? + PathSep* = ',' + FileSystemCaseSensitive* = true + ExeExt* = "" + ScriptExt* = "" + DynlibFormat* = "lib$1.so" +else: # UNIX-like operating system + const + CurDir* = '.' + ParDir* = ".." + DirSep* = '/' + AltSep* = DirSep + PathSep* = ':' + FileSystemCaseSensitive* = when defined(macosx): false else: true + ExeExt* = "" + ScriptExt* = "" + DynlibFormat* = when defined(macosx): "lib$1.dylib" else: "lib$1.so" + +const + ExtSep* = '.' + ## The character which separates the base filename from the extension; + ## for example, the '.' in ``os.nim``. + +proc normalizePathEnd(path: var string, trailingSep = false) = + ## ensures ``path`` has exactly 0 or 1 trailing `DirSep`, depending on + ## ``trailingSep``, and taking care of edge cases: it preservers whether + ## a path is absolute or relative, and makes sure trailing sep is `DirSep`, + ## not `AltSep`. + if path.len == 0: return + var i = path.len + while i >= 1 and path[i-1] in {DirSep, AltSep}: dec(i) + if trailingSep: + # foo// => foo + path.setLen(i) + # foo => foo/ + path.add DirSep + elif i>0: + # foo// => foo + path.setLen(i) + else: + # // => / (empty case was already taken care of) + path = $DirSep + +proc normalizePathEnd(path: string, trailingSep = false): string = + result = path + result.normalizePathEnd(trailingSep) + +proc joinPath*(head, tail: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Joins two directory names to one. + ## + ## For example on Unix: + ## + ## .. code-block:: nim + ## joinPath("usr", "lib") + ## + ## results in: + ## + ## .. code-block:: nim + ## "usr/lib" + ## + ## If head is the empty string, tail is returned. If tail is the empty + ## string, head is returned with a trailing path separator. If tail starts + ## with a path separator it will be removed when concatenated to head. Other + ## path separators not located on boundaries won't be modified. More + ## examples on Unix: + ## + ## .. code-block:: nim + ## assert joinPath("usr", "") == "usr/" + ## assert joinPath("", "lib") == "lib" + ## assert joinPath("", "/lib") == "/lib" + ## assert joinPath("usr/", "/lib") == "usr/lib" + if len(head) == 0: + result = tail + elif head[len(head)-1] in {DirSep, AltSep}: + if tail.len > 0 and tail[0] in {DirSep, AltSep}: + result = head & substr(tail, 1) + else: + result = head & tail + else: + if tail.len > 0 and tail[0] in {DirSep, AltSep}: + result = head & tail + else: + result = head & DirSep & tail + +proc joinPath*(parts: varargs[string]): string {.noSideEffect, + rtl, extern: "nos$1OpenArray".} = + ## 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 + ## will assert in debug builds and crash on release builds. + result = parts[0] + for i in 1..high(parts): + result = joinPath(result, parts[i]) + +proc `/` * (head, tail: string): string {.noSideEffect.} = + ## The same as ``joinPath(head, tail)`` + ## + ## Here are some examples for Unix: + ## + ## .. code-block:: nim + ## assert "usr" / "" == "usr/" + ## assert "" / "lib" == "lib" + ## assert "" / "/lib" == "/lib" + ## assert "usr/" / "/lib" == "usr/lib" + return joinPath(head, tail) + +proc splitPath*(path: string): tuple[head, tail: string] {. + noSideEffect, rtl, extern: "nos$1".} = + ## Splits a directory into (head, tail), so that + ## ``head / tail == path`` (except for edge cases like "/usr"). + ## + ## Examples: + ## + ## .. code-block:: nim + ## splitPath("usr/local/bin") -> ("usr/local", "bin") + ## splitPath("usr/local/bin/") -> ("usr/local/bin", "") + ## splitPath("bin") -> ("", "bin") + ## splitPath("/bin") -> ("", "bin") + ## splitPath("") -> ("", "") + var sepPos = -1 + for i in countdown(len(path)-1, 0): + if path[i] in {DirSep, AltSep}: + sepPos = i + break + if sepPos >= 0: + result.head = substr(path, 0, sepPos-1) + result.tail = substr(path, sepPos+1) + else: + result.head = "" + result.tail = path + +proc parentDirPos(path: string): int = + var q = 1 + if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2 + for i in countdown(len(path)-q, 0): + if path[i] in {DirSep, AltSep}: return i + result = -1 + +proc parentDir*(path: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Returns the parent directory of `path`. + ## + ## This is the same as ``splitPath(path).head`` when ``path`` doesn't end + ## in a dir separator. + ## The remainder can be obtained with ``lastPathPart(path)`` + runnableExamples: + doAssert parentDir("") == "" + when defined(posix): + doAssert parentDir("/usr/local/bin") == "/usr/local" + doAssert parentDir("foo/bar/") == "foo" + + let sepPos = parentDirPos(path) + if sepPos >= 0: + result = substr(path, 0, sepPos-1) + else: + result = "" + +proc tailDir*(path: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Returns the tail part of `path`.. + ## + ## | Example: ``tailDir("/usr/local/bin") == "local/bin"``. + ## | Example: ``tailDir("usr/local/bin/") == "local/bin"``. + ## | Example: ``tailDir("bin") == ""``. + var q = 1 + if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2 + for i in 0..len(path)-q: + if path[i] in {DirSep, AltSep}: + return substr(path, i+1) + result = "" + +proc isRootDir*(path: string): bool {. + noSideEffect, rtl, extern: "nos$1".} = + ## Checks whether a given `path` is a root directory + result = parentDirPos(path) < 0 + +iterator parentDirs*(path: string, fromRoot=false, inclusive=true): string = + ## Walks over all parent directories of a given `path` + ## + ## If `fromRoot` is set, the traversal will start from the file system root + ## diretory. If `inclusive` is set, the original argument will be included + ## in the traversal. + ## + ## Relative paths won't be expanded by this proc. Instead, it will traverse + ## only the directories appearing in the relative path. + if not fromRoot: + var current = path + if inclusive: yield path + while true: + if current.isRootDir: break + current = current.parentDir + yield current + else: + for i in countup(0, path.len - 2): # ignore the last / + # deal with non-normalized paths such as /foo//bar//baz + if path[i] in {DirSep, AltSep} and + (i == 0 or path[i-1] notin {DirSep, AltSep}): + yield path.substr(0, i) + + if inclusive: yield path + +proc `/../`*(head, tail: string): string {.noSideEffect.} = + ## The same as ``parentDir(head) / tail`` unless there is no parent + ## directory. Then ``head / tail`` is performed instead. + let sepPos = parentDirPos(head) + if sepPos >= 0: + result = substr(head, 0, sepPos-1) / tail + else: + result = head / tail + +proc normExt(ext: string): string = + if ext == "" or ext[0] == ExtSep: result = ext # no copy needed here + else: result = ExtSep & ext + +proc searchExtPos*(path: string): int = + ## Returns index of the '.' char in `path` if it signifies the beginning + ## of extension. Returns -1 otherwise. + # BUGFIX: do not search until 0! .DS_Store is no file extension! + result = -1 + for i in countdown(len(path)-1, 1): + if path[i] == ExtSep: + result = i + break + elif path[i] in {DirSep, AltSep}: + break # do not skip over path + +proc splitFile*(path: string): tuple[dir, name, ext: string] {. + noSideEffect, rtl, extern: "nos$1".} = + ## Splits a filename into (dir, filename, extension). + ## `dir` does not end in `DirSep`. + ## `extension` includes the leading dot. + ## + ## Example: + ## + ## .. code-block:: nim + ## var (dir, name, ext) = splitFile("usr/local/nimc.html") + ## assert dir == "usr/local" + ## assert name == "nimc" + ## assert ext == ".html" + ## + ## If `path` has no extension, `ext` is the empty string. + ## If `path` has no directory component, `dir` is the empty string. + ## If `path` has no filename component, `name` and `ext` are empty strings. + if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: + result = (path, "", "") + else: + var sepPos = -1 + var dotPos = path.len + for i in countdown(len(path)-1, 0): + if path[i] == ExtSep: + if dotPos == path.len and i > 0 and + path[i-1] notin {DirSep, AltSep}: dotPos = i + elif path[i] in {DirSep, AltSep}: + sepPos = i + break + result.dir = substr(path, 0, sepPos-1) + result.name = substr(path, sepPos+1, dotPos-1) + result.ext = substr(path, dotPos) + +proc extractFilename*(path: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Extracts the filename of a given `path`. This is the same as + ## ``name & ext`` from ``splitFile(path)``. See also ``lastPathPart``. + runnableExamples: + when defined(posix): + doAssert extractFilename("foo/bar/") == "" + doAssert extractFilename("foo/bar") == "bar" + if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: + result = "" + else: + result = splitPath(path).tail + +proc lastPathPart*(path: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## like ``extractFilename``, but ignores trailing dir separator; aka: `baseName`:idx: + ## in some other languages. + runnableExamples: + when defined(posix): + doAssert lastPathPart("foo/bar/") == "bar" + let path = path.normalizePathEnd(trailingSep = false) + result = extractFilename(path) -proc c_rename(oldname, newname: cstring): cint {. - importc: "rename", header: "<stdio.h>".} -proc c_system(cmd: cstring): cint {. - importc: "system", header: "<stdlib.h>".} -proc c_strlen(a: cstring): cint {. - importc: "strlen", header: "<string.h>", noSideEffect.} -proc c_free(p: pointer) {. - importc: "free", header: "<stdlib.h>".} +proc changeFileExt*(filename, ext: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Changes the file extension to `ext`. + ## + ## If the `filename` has no extension, `ext` will be added. + ## If `ext` == "" then any extension is removed. + ## `Ext` should be given without the leading '.', because some + ## filesystems may use a different character. (Although I know + ## of none such beast.) + var extPos = searchExtPos(filename) + if extPos < 0: result = filename & normExt(ext) + else: result = substr(filename, 0, extPos-1) & normExt(ext) + +proc addFileExt*(filename, ext: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Adds the file extension `ext` to `filename`, unless + ## `filename` already has an extension. + ## + ## `Ext` should be given without the leading '.', because some + ## filesystems may use a different character. + ## (Although I know of none such beast.) + var extPos = searchExtPos(filename) + if extPos < 0: result = filename & normExt(ext) + else: result = filename + +proc cmpPaths*(pathA, pathB: string): int {. + noSideEffect, rtl, extern: "nos$1".} = + ## Compares two paths. + ## + ## On a case-sensitive filesystem this is done + ## case-sensitively otherwise case-insensitively. Returns: + ## + ## | 0 iff pathA == pathB + ## | < 0 iff pathA < pathB + ## | > 0 iff pathA > pathB + runnableExamples: + when defined(macosx): + doAssert cmpPaths("foo", "Foo") == 0 + elif defined(posix): + doAssert cmpPaths("foo", "Foo") > 0 + if FileSystemCaseSensitive: + result = cmp(pathA, pathB) + else: + when defined(nimscript): + result = cmpic(pathA, pathB) + elif defined(nimdoc): discard + else: + result = cmpIgnoreCase(pathA, pathB) + +proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} = + ## Checks whether a given `path` is absolute. + ## + ## On Windows, network paths are considered absolute too. + runnableExamples: + doAssert(not "".isAbsolute) + doAssert(not ".".isAbsolute) + when defined(posix): + doAssert "/".isAbsolute + doAssert(not "a/".isAbsolute) + if len(path) == 0: return false -when defined(windows): + when doslikeFileSystem: + var len = len(path) + result = (path[0] in {'/', '\\'}) or + (len > 1 and path[0] in {'a'..'z', 'A'..'Z'} and path[1] == ':') + elif defined(macos): + # according to https://perldoc.perl.org/File/Spec/Mac.html `:a` is a relative path + result = path[0] != ':' + elif defined(RISCOS): + result = path[0] == '$' + elif defined(posix): + result = path[0] == '/' + +proc unixToNativePath*(path: string, drive=""): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Converts an UNIX-like path to a native one. + ## + ## On an UNIX system this does nothing. Else it converts + ## '/', '.', '..' to the appropriate things. + ## + ## On systems with a concept of "drives", `drive` is used to determine + ## which drive label to use during absolute path conversion. + ## `drive` defaults to the drive of the current working directory, and is + ## ignored on systems that do not have a concept of "drives". + + when defined(unix): + result = path + else: + if path.len == 0: + return "" + + var start: int + if path[0] == '/': + # an absolute path + when doslikeFileSystem: + if drive != "": + result = drive & ":" & DirSep + else: + result = $DirSep + elif defined(macos): + result = "" # must not start with ':' + else: + result = $DirSep + start = 1 + elif path[0] == '.' and (path.len == 1 or path[1] == '/'): + # current directory + result = $CurDir + start = when doslikeFileSystem: 1 else: 2 + else: + result = "" + start = 0 + + var i = start + while i < len(path): # ../../../ --> :::: + if i+2 < path.len and path[i] == '.' and path[i+1] == '.' and path[i+2] == '/': + # parent directory + when defined(macos): + if result[high(result)] == ':': + add result, ':' + else: + add result, ParDir + else: + add result, ParDir & DirSep + inc(i, 3) + elif path[i] == '/': + add result, DirSep + inc(i) + else: + add result, path[i] + inc(i) + +include "includes/oserr" +when not defined(nimscript): + include "includes/osenv" + +proc getHomeDir*(): string {.rtl, extern: "nos$1", + tags: [ReadEnvEffect, ReadIOEffect].} = + ## Returns the home directory of the current user. + ## + ## This proc is wrapped by the expandTilde proc for the convenience of + ## processing paths coming from user configuration files. + when defined(windows): return string(getEnv("USERPROFILE")) & "\\" + else: return string(getEnv("HOME")) & "/" + +proc getConfigDir*(): string {.rtl, extern: "nos$1", + tags: [ReadEnvEffect, ReadIOEffect].} = + ## Returns the config directory of the current user for applications. + ## + ## On non-Windows OSs, this proc conforms to the XDG Base Directory + ## spec. Thus, this proc returns the value of the XDG_CONFIG_HOME environment + ## variable if it is set, and returns the default configuration directory, + ## "~/.config/", otherwise. + ## + ## An OS-dependent trailing slash is always present at the end of the + ## returned string; `\` on Windows and `/` on all other OSs. + when defined(windows): + result = getEnv("APPDATA").string + else: + result = getEnv("XDG_CONFIG_HOME", getEnv("HOME").string / ".config").string + result.normalizePathEnd(trailingSep = true) + +proc getTempDir*(): string {.rtl, extern: "nos$1", + tags: [ReadEnvEffect, ReadIOEffect].} = + ## Returns the temporary directory of the current user for applications to + ## save temporary files in. + ## + ## **Please do not use this**: On Android, it currently + ## returns ``getHomeDir()``, and on other Unix based systems it can cause + ## security problems too. That said, you can override this implementation + ## by adding ``-d:tempDir=mytempname`` to your compiler invokation. + when defined(tempDir): + const tempDir {.strdefine.}: string = nil + return tempDir + elif defined(windows): return string(getEnv("TEMP")) & "\\" + elif defined(android): return getHomeDir() + else: return "/tmp/" + +proc expandTilde*(path: string): string {. + tags: [ReadEnvEffect, ReadIOEffect].} = + ## Expands ``~`` or a path starting with ``~/`` to a full path, replacing + ## ``~`` with ``getHomeDir()`` (otherwise returns ``path`` unmodified). + ## + ## Windows: this is still supported despite Windows platform not having this + ## convention; also, both ``~/`` and ``~\`` are handled. + runnableExamples: + doAssert expandTilde("~" / "appname.cfg") == getHomeDir() / "appname.cfg" + if len(path) == 0 or path[0] != '~': + result = path + elif len(path) == 1: + result = getHomeDir() + elif (path[1] in {DirSep, AltSep}): + result = getHomeDir() / path.substr(2) + else: + # TODO: handle `~bob` and `~bob/` which means home of bob + result = path + +# TODO: consider whether quoteShellPosix, quoteShellWindows, quoteShell, quoteShellCommand +# belong in `strutils` instead; they are not specific to paths +proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = + ## Quote s, so it can be safely passed to Windows API. + ## Based on Python's subprocess.list2cmdline + ## See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx + let needQuote = {' ', '\t'} in s or s.len == 0 + + result = "" + var backslashBuff = "" + if needQuote: + result.add("\"") + + for c in s: + if c == '\\': + backslashBuff.add(c) + elif c == '\"': + result.add(backslashBuff) + result.add(backslashBuff) + backslashBuff.setLen(0) + result.add("\\\"") + else: + if backslashBuff.len != 0: + result.add(backslashBuff) + backslashBuff.setLen(0) + result.add(c) + + if needQuote: + result.add("\"") + +proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = + ## Quote ``s``, so it can be safely passed to POSIX shell. + ## Based on Python's pipes.quote + const safeUnixChars = {'%', '+', '-', '.', '/', '_', ':', '=', '@', + '0'..'9', 'A'..'Z', 'a'..'z'} + if s.len == 0: + return "''" + + let safe = s.allCharsInSet(safeUnixChars) + + if safe: + return s + else: + return "'" & s.replace("'", "'\"'\"'") & "'" + +when defined(windows) or defined(posix) or defined(nintendoswitch): + proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = + ## Quote ``s``, so it can be safely passed to shell. + when defined(windows): + return quoteShellWindows(s) + else: + return quoteShellPosix(s) + + proc quoteShellCommand*(args: openArray[string]): string = + ## Concatenates and quotes shell arguments `args` + runnableExamples: + when defined(posix): + assert quoteShellCommand(["aaa", "", "c d"]) == "aaa '' 'c d'" + when defined(windows): + assert quoteShellCommand(["aaa", "", "c d"]) == "aaa \"\" \"c d\"" + # can't use `map` pending https://github.com/nim-lang/Nim/issues/8303 + for i in 0..<args.len: + if i > 0: result.add " " + result.add quoteShell(args[i]) + +when not defined(nimscript): + proc c_rename(oldname, newname: cstring): cint {. + importc: "rename", header: "<stdio.h>".} + proc c_system(cmd: cstring): cint {. + importc: "system", header: "<stdlib.h>".} + proc c_strlen(a: cstring): cint {. + importc: "strlen", header: "<string.h>", noSideEffect.} + proc c_free(p: pointer) {. + importc: "free", header: "<stdlib.h>".} + + +when defined(windows) and not defined(nimscript): when useWinUnicode: template wrapUnary(varname, winApiProc, arg: untyped) = var varname = winApiProc(newWideCString(arg)) @@ -71,9 +739,10 @@ when defined(windows): f.cFileName[1].int == dot and f.cFileName[2].int == 0) proc existsFile*(filename: string): bool {.rtl, extern: "nos$1", - tags: [ReadDirEffect].} = + tags: [ReadDirEffect], noNimScript.} = ## Returns true if `filename` exists and is a regular file or symlink. ## (directories, device files, named pipes and sockets return false) + ## This proc is not available for NimScript. when defined(windows): when useWinUnicode: wrapUnary(a, getFileAttributesW, filename) @@ -85,7 +754,8 @@ proc existsFile*(filename: string): bool {.rtl, extern: "nos$1", var res: Stat return stat(filename, res) >= 0'i32 and S_ISREG(res.st_mode) -proc existsDir*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect].} = +proc existsDir*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect], + noNimScript.} = ## Returns true iff the directory `dir` exists. If `dir` is a file, false ## is returned. Follows symlinks. when defined(windows): @@ -100,7 +770,8 @@ proc existsDir*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect] return stat(dir, res) >= 0'i32 and S_ISDIR(res.st_mode) proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1", - tags: [ReadDirEffect].} = + tags: [ReadDirEffect], + noNimScript.} = ## Returns true iff the symlink `link` exists. Will return true ## regardless of whether the link points to a directory or file. when defined(windows): @@ -114,15 +785,15 @@ proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1", var res: Stat return lstat(link, res) >= 0'i32 and S_ISLNK(res.st_mode) -proc fileExists*(filename: string): bool {.inline.} = +proc fileExists*(filename: string): bool {.inline, noNimScript.} = ## Synonym for existsFile existsFile(filename) -proc dirExists*(dir: string): bool {.inline.} = +proc dirExists*(dir: string): bool {.inline, noNimScript.} = ## Synonym for existsDir existsDir(dir) -when not defined(windows): +when not defined(windows) and not defined(nimscript): proc checkSymlink(path: string): bool = var rawInfo: Stat if lstat(path, rawInfo) < 0'i32: result = false @@ -135,7 +806,7 @@ const proc findExe*(exe: string, followSymlinks: bool = true; extensions: openarray[string]=ExeExts): string {. - tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect].} = + tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect], noNimScript.} = ## 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` @@ -183,7 +854,11 @@ proc findExe*(exe: string, followSymlinks: bool = true; return x result = "" -proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1".} = +when defined(nimscript): + const times = "fake const" + template Time(x: untyped): untyped = string + +proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1", noNimScript.} = ## Returns the `file`'s last modification time. when defined(posix): var res: Stat @@ -196,7 +871,7 @@ proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1".} result = fromWinTime(rdFileTime(f.ftLastWriteTime)) findClose(h) -proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1".} = +proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1", noNimScript.} = ## Returns the `file`'s last read or write access time. when defined(posix): var res: Stat @@ -209,7 +884,7 @@ proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1".} = result = fromWinTime(rdFileTime(f.ftLastAccessTime)) findClose(h) -proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1".} = +proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1", noNimScript.} = ## Returns the `file`'s creation time. ## ## **Note:** Under POSIX OS's, the returned time may actually be the time at @@ -226,7 +901,7 @@ proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1".} = result = fromWinTime(rdFileTime(f.ftCreationTime)) findClose(h) -proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1".} = +proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1", noNimScript.} = ## Returns true if the file `a` is newer than file `b`, i.e. if `a`'s ## modification time is later than `b`'s. when defined(posix): @@ -238,7 +913,7 @@ proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1".} = else: result = getLastModificationTime(a) > getLastModificationTime(b) -proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} = +proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [], noNimScript.} = ## Returns the `current working directory`:idx:. when defined(windows): var bufsize = MAX_PATH.int32 @@ -282,7 +957,7 @@ proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} = else: raiseOSError(osLastError()) -proc setCurrentDir*(newDir: string) {.inline, tags: [].} = +proc setCurrentDir*(newDir: string) {.inline, tags: [], noNimScript.} = ## Sets the `current working directory`:idx:; `OSError` is raised if ## `newDir` cannot been set. when defined(Windows): @@ -294,19 +969,20 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [].} = else: if chdir(newDir) != 0'i32: raiseOSError(osLastError()) -proc absolutePath*(path: string, root = getCurrentDir()): string = - ## Returns the absolute path of `path`, rooted at `root` (which must be absolute) - ## if `path` is absolute, return it, ignoring `root` - runnableExamples: - doAssert absolutePath("a") == getCurrentDir() / "a" - if isAbsolute(path): path - else: - if not root.isAbsolute: - raise newException(ValueError, "The specified root is not absolute: " & root) - joinPath(root, path) +when not defined(nimscript): + proc absolutePath*(path: string, root = getCurrentDir()): string {.noNimScript.} = + ## Returns the absolute path of `path`, rooted at `root` (which must be absolute) + ## if `path` is absolute, return it, ignoring `root` + runnableExamples: + doAssert absolutePath("a") == getCurrentDir() / "a" + if isAbsolute(path): path + else: + if not root.isAbsolute: + raise newException(ValueError, "The specified root is not absolute: " & root) + joinPath(root, path) proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", - tags: [ReadDirEffect].} = + tags: [ReadDirEffect], noNimScript.} = ## Returns the full (`absolute`:idx:) path of an existing file `filename`, ## raises OSError in case of an error. Follows symlinks. when defined(windows): @@ -347,7 +1023,7 @@ proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", result = $r c_free(cast[pointer](r)) -proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [].} = +proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [], noNimScript.} = ## Normalize a path. ## ## Consecutive directory separators are collapsed, including an initial double slash. @@ -383,12 +1059,12 @@ proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [].} = else: path = "." -proc normalizedPath*(path: string): string {.rtl, extern: "nos$1", tags: [].} = +proc normalizedPath*(path: string): string {.rtl, extern: "nos$1", tags: [], noNimScript.} = ## Returns a normalized path for the current OS. See `<#normalizePath>`_ result = path normalizePath(result) -when defined(Windows): +when defined(Windows) and not defined(nimscript): proc openHandle(path: string, followSymlink=true, writeAccess=false): Handle = var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL if not followSymlink: @@ -409,7 +1085,7 @@ when defined(Windows): ) proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", - tags: [ReadDirEffect].} = + tags: [ReadDirEffect], noNimScript.} = ## Returns true if both pathname arguments refer to the same physical ## file or directory. Raises an exception if any of the files does not ## exist or information about it can not be obtained. @@ -449,7 +1125,7 @@ proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", result = a.st_dev == b.st_dev and a.st_ino == b.st_ino proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1", - tags: [ReadIOEffect].} = + tags: [ReadIOEffect], noNimScript.} = ## Returns true if both pathname arguments refer to files with identical ## binary content. const @@ -492,7 +1168,7 @@ type fpOthersRead ## read access for others proc getFilePermissions*(filename: string): set[FilePermission] {. - rtl, extern: "nos$1", tags: [ReadDirEffect].} = + rtl, extern: "nos$1", tags: [ReadDirEffect], noNimScript.} = ## retrieves file permissions for `filename`. `OSError` is raised in case of ## an error. On Windows, only the ``readonly`` flag is checked, every other ## permission is available in any case. @@ -524,7 +1200,7 @@ proc getFilePermissions*(filename: string): set[FilePermission] {. result = {fpUserExec..fpOthersRead} proc setFilePermissions*(filename: string, permissions: set[FilePermission]) {. - rtl, extern: "nos$1", tags: [WriteDirEffect].} = + rtl, extern: "nos$1", tags: [WriteDirEffect], noNimScript.} = ## sets the file permissions for `filename`. `OSError` is raised in case of ## an error. On Windows, only the ``readonly`` flag is changed, depending on ## ``fpUserWrite``. @@ -560,7 +1236,7 @@ proc setFilePermissions*(filename: string, permissions: set[FilePermission]) {. if res2 == - 1'i32: raiseOSError(osLastError()) proc copyFile*(source, dest: string) {.rtl, extern: "nos$1", - tags: [ReadIOEffect, WriteIOEffect].} = + tags: [ReadIOEffect, WriteIOEffect], noNimScript.} = ## Copies a file from `source` to `dest`. ## ## If this fails, `OSError` is raised. On the Windows platform this proc will @@ -611,7 +1287,7 @@ when not declared(ENOENT) and not defined(Windows): else: var ENOENT {.importc, header: "<errno.h>".}: cint -when defined(Windows): +when defined(Windows) and not defined(nimscript): when useWinUnicode: template deleteFile(file: untyped): untyped = deleteFileW(file) template setFileAttributes(file, attrs: untyped): untyped = @@ -621,7 +1297,7 @@ when defined(Windows): template setFileAttributes(file, attrs: untyped): untyped = setFileAttributesA(file, attrs) -proc tryRemoveFile*(file: string): bool {.rtl, extern: "nos$1", tags: [WriteDirEffect].} = +proc tryRemoveFile*(file: string): bool {.rtl, extern: "nos$1", tags: [WriteDirEffect], noNimScript.} = ## Removes the `file`. If this fails, returns `false`. This does not fail ## if the file never existed in the first place. ## On Windows, ignores the read-only attribute. @@ -644,7 +1320,7 @@ proc tryRemoveFile*(file: string): bool {.rtl, extern: "nos$1", tags: [WriteDirE if unlink(file) != 0'i32 and errno != ENOENT: result = false -proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} = +proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect], noNimScript.} = ## Removes the `file`. If this fails, `OSError` is raised. This does not fail ## if the file never existed in the first place. ## On Windows, ignores the read-only attribute. @@ -654,7 +1330,7 @@ proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} else: raiseOSError(osLastError(), $strerror(errno)) -proc tryMoveFSObject(source, dest: string): bool = +proc tryMoveFSObject(source, dest: string): bool {.noNimScript.} = ## Moves a file or directory from `source` to `dest`. Returns false in case ## of `EXDEV` error. In case of other errors `OSError` is raised. Returns ## true in case of success. @@ -675,7 +1351,7 @@ proc tryMoveFSObject(source, dest: string): bool = return true proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", - tags: [ReadIOEffect, WriteIOEffect].} = + tags: [ReadIOEffect, WriteIOEffect], noNimScript.} = ## Moves a file from `source` to `dest`. If this fails, `OSError` is raised. ## Can be used to `rename files`:idx: if not tryMoveFSObject(source, dest): @@ -689,7 +1365,7 @@ proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", raise proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", - tags: [ExecIOEffect].} = + tags: [ExecIOEffect], noNimScript.} = ## Executes a `shell command`:idx:. ## ## Command has the form 'program args' where args are the command @@ -704,7 +1380,7 @@ proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", result = c_system(command) # Templates for filtering directories and files -when defined(windows): +when defined(windows) and not defined(nimscript): template isDir(f: WIN32_FIND_DATA): bool = (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32 template isFile(f: WIN32_FIND_DATA): bool = @@ -760,7 +1436,7 @@ template walkCommon(pattern: string, filter) = if filter(path): yield path -iterator walkPattern*(pattern: string): string {.tags: [ReadDirEffect].} = +iterator walkPattern*(pattern: string): string {.tags: [ReadDirEffect], noNimScript.} = ## Iterate over all the files and directories that match the `pattern`. ## On POSIX this uses the `glob`:idx: call. ## @@ -768,7 +1444,7 @@ iterator walkPattern*(pattern: string): string {.tags: [ReadDirEffect].} = ## notation is supported. walkCommon(pattern, defaultWalkFilter) -iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} = +iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect], noNimScript.} = ## Iterate over all the files that match the `pattern`. On POSIX this uses ## the `glob`:idx: call. ## @@ -776,7 +1452,7 @@ iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} = ## notation is supported. walkCommon(pattern, isFile) -iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect].} = +iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect], noNimScript.} = ## Iterate over all the directories that match the `pattern`. ## On POSIX this uses the `glob`:idx: call. ## @@ -791,7 +1467,7 @@ type pcDir, ## path refers to a directory pcLinkToDir ## path refers to a symbolic link to a directory -when defined(posix): +when defined(posix) and not defined(nimscript): proc getSymlinkFileKind(path: string): PathComponent = # Helper function. var s: Stat @@ -832,7 +1508,10 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: for k, v in items(staticWalkDir(dir, relative)): yield (k, v) else: - when defined(windows): + when defined(nimscript): + for k, v in items(staticWalkDir(dir, relative)): + yield (k, v) + elif defined(windows): var f: WIN32_FIND_DATA var h = findFirstFile(dir / "*", f) if h != -1: @@ -920,7 +1599,7 @@ iterator walkDirRec*(dir: string, yieldFilter = {pcFile}, if k in yieldFilter: yield p -proc rawRemoveDir(dir: string) = +proc rawRemoveDir(dir: string) {.noNimScript.} = when defined(windows): when useWinUnicode: wrapUnary(res, removeDirectoryW, dir) @@ -934,7 +1613,7 @@ proc rawRemoveDir(dir: string) = if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError()) proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [ - WriteDirEffect, ReadDirEffect], benign.} = + WriteDirEffect, ReadDirEffect], benign, noNimScript.} = ## Removes the directory `dir` including all subdirectories and files ## in `dir` (recursively). ## @@ -946,7 +1625,7 @@ proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [ of pcDir: removeDir(path) rawRemoveDir(dir) -proc rawCreateDir(dir: string): bool = +proc rawCreateDir(dir: string): bool {.noNimScript.} = # Try to create one directory (not the whole path). # returns `true` for success, `false` if the path has previously existed # @@ -991,7 +1670,7 @@ proc rawCreateDir(dir: string): bool = raiseOSError(osLastError(), dir) proc existsOrCreateDir*(dir: string): bool {.rtl, extern: "nos$1", - tags: [WriteDirEffect, ReadDirEffect].} = + tags: [WriteDirEffect, ReadDirEffect], noNimScript.} = ## Check if a `directory`:idx: `dir` exists, and create it otherwise. ## ## Does not create parent directories (fails if parent does not exist). @@ -1004,7 +1683,7 @@ proc existsOrCreateDir*(dir: string): bool {.rtl, extern: "nos$1", raise newException(IOError, "Failed to create '" & dir & "'") proc createDir*(dir: string) {.rtl, extern: "nos$1", - tags: [WriteDirEffect, ReadDirEffect].} = + tags: [WriteDirEffect, ReadDirEffect], noNimScript.} = ## Creates the `directory`:idx: `dir`. ## ## The directory may contain several subdirectories that do not exist yet. @@ -1027,7 +1706,7 @@ proc createDir*(dir: string) {.rtl, extern: "nos$1", discard existsOrCreateDir(dir) proc copyDir*(source, dest: string) {.rtl, extern: "nos$1", - tags: [WriteIOEffect, ReadIOEffect], benign.} = + tags: [WriteIOEffect, ReadIOEffect], benign, noNimScript.} = ## Copies a directory from `source` to `dest`. ## ## If this fails, `OSError` is raised. On the Windows platform this proc will @@ -1045,7 +1724,7 @@ proc copyDir*(source, dest: string) {.rtl, extern: "nos$1", copyDir(path, dest / noSource) else: discard -proc createSymlink*(src, dest: string) = +proc createSymlink*(src, dest: string) {.noNimScript.} = ## Create a symbolic link at `dest` which points to the item specified ## by `src`. On most operating systems, will fail if a link already exists. ## @@ -1068,7 +1747,7 @@ proc createSymlink*(src, dest: string) = if symlink(src, dest) != 0: raiseOSError(osLastError()) -proc createHardlink*(src, dest: string) = +proc createHardlink*(src, dest: string) {.noNimScript.} = ## Create a hard link at `dest` which points to the item specified ## by `src`. ## @@ -1176,7 +1855,7 @@ proc parseCmdLine*(c: string): seq[string] {. add(result, a) proc copyFileWithPermissions*(source, dest: string, - ignorePermissionErrors = true) = + ignorePermissionErrors = true) {.noNimScript.} = ## Copies a file from `source` to `dest` preserving file permissions. ## ## This is a wrapper proc around `copyFile() <#copyFile>`_, @@ -1200,7 +1879,7 @@ proc copyFileWithPermissions*(source, dest: string, proc copyDirWithPermissions*(source, dest: string, ignorePermissionErrors = true) {.rtl, extern: "nos$1", - tags: [WriteIOEffect, ReadIOEffect], benign.} = + tags: [WriteIOEffect, ReadIOEffect], benign, noNimScript.} = ## Copies a directory from `source` to `dest` preserving file permissions. ## ## If this fails, `OSError` is raised. This is a wrapper proc around `copyDir() @@ -1231,7 +1910,7 @@ proc copyDirWithPermissions*(source, dest: string, proc inclFilePermissions*(filename: string, permissions: set[FilePermission]) {. - rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect].} = + rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noNimScript.} = ## a convenience procedure for: ## ## .. code-block:: nim @@ -1240,14 +1919,14 @@ proc inclFilePermissions*(filename: string, proc exclFilePermissions*(filename: string, permissions: set[FilePermission]) {. - rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect].} = + rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noNimScript.} = ## a convenience procedure for: ## ## .. code-block:: nim ## setFilePermissions(filename, getFilePermissions(filename)-permissions) setFilePermissions(filename, getFilePermissions(filename)-permissions) -proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect].} = +proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect], noNimScript.} = ## Moves a directory from `source` to `dest`. If this fails, `OSError` is raised. if not tryMoveFSObject(source, dest): when not defined(windows): @@ -1257,7 +1936,7 @@ proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect].} = #include ospaths -proc expandSymlink*(symlinkPath: string): string = +proc expandSymlink*(symlinkPath: string): string {.noNimScript.} = ## Returns a string representing the path to which the symbolic link points. ## ## On Windows this is a noop, ``symlinkPath`` is simply returned. @@ -1320,6 +1999,13 @@ when defined(nimdoc): ## else: ## # Do something else! +elif defined(nintendoswitch) or defined(nimscript): + proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} = + raise newException(OSError, "paramStr is not implemented on Nintendo Switch") + + proc paramCount*(): int {.tags: [ReadIOEffect].} = + raise newException(OSError, "paramCount is not implemented on Nintendo Switch") + elif defined(windows): # Since we support GUI applications with Nim, we sometimes generate # a WinMain entry proc. But a WinMain proc has no access to the parsed @@ -1346,13 +2032,6 @@ elif defined(windows): if i < ownArgv.len and i >= 0: return TaintedString(ownArgv[i]) raise newException(IndexError, "invalid index") -elif defined(nintendoswitch): - proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} = - raise newException(OSError, "paramStr is not implemented on Nintendo Switch") - - proc paramCount*(): int {.tags: [ReadIOEffect].} = - raise newException(OSError, "paramCount is not implemented on Nintendo Switch") - elif defined(genode): proc paramStr*(i: int): TaintedString = raise newException(OSError, "paramStr is not implemented on Genode") @@ -1397,7 +2076,7 @@ when declared(paramCount) or defined(nimdoc): for i in 1..paramCount(): result.add(paramStr(i)) -when defined(freebsd) or defined(dragonfly): +when not defined(nimscript) and (defined(freebsd) or defined(dragonfly)): proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize, newp: pointer, newplen: csize): cint {.importc: "sysctl",header: """#include <sys/types.h> @@ -1430,7 +2109,7 @@ when defined(freebsd) or defined(dragonfly): result.setLen(pathLength) break -when defined(linux) or defined(solaris) or defined(bsd) or defined(aix): +when not defined(nimscript) and (defined(linux) or defined(solaris) or defined(bsd) or defined(aix)): proc getApplAux(procPath: string): string = result = newString(256) var len = readlink(procPath, result, 256) @@ -1439,7 +2118,7 @@ when defined(linux) or defined(solaris) or defined(bsd) or defined(aix): len = readlink(procPath, result, len) setLen(result, len) -when not (defined(windows) or defined(macosx)): +when not (defined(windows) or defined(macosx) or defined(nimscript)): proc getApplHeuristic(): string = when declared(paramStr): result = string(paramStr(0)) @@ -1483,7 +2162,7 @@ when defined(haiku): else: result = "" -proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = +proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noNimScript.} = ## Returns the filename of the application's executable. ## ## This procedure will resolve symlinks. @@ -1543,11 +2222,11 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = if result.len == 0: result = getApplHeuristic() -proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = +proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noNimScript.} = ## Returns the directory of the application's executable. result = splitFile(getAppFilename()).dir -proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect].} = +proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect], noNimScript.} = ## sleeps `milsecs` milliseconds. when defined(windows): winlean.sleep(int32(milsecs)) @@ -1558,7 +2237,7 @@ proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect].} = discard posix.nanosleep(a, b) proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1", - tags: [ReadIOEffect].} = + tags: [ReadIOEffect], noNimScript.} = ## returns the file size of `file` (in bytes). An ``OSError`` exception is ## raised in case of an error. when defined(windows): @@ -1574,7 +2253,7 @@ proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1", close(f) else: raiseOSError(osLastError()) -when defined(Windows): +when defined(Windows) or defined(nimscript): type DeviceId* = int32 FileId* = int64 @@ -1654,7 +2333,7 @@ template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped = assert(path != "") # symlinks can't occur for file handles formalInfo.kind = getSymlinkFileKind(path) -proc getFileInfo*(handle: FileHandle): FileInfo = +proc getFileInfo*(handle: FileHandle): FileInfo {.noNimScript.} = ## Retrieves file information for the file object represented by the given ## handle. ## @@ -1675,12 +2354,12 @@ proc getFileInfo*(handle: FileHandle): FileInfo = raiseOSError(osLastError()) rawToFormalFileInfo(rawInfo, "", result) -proc getFileInfo*(file: File): FileInfo = +proc getFileInfo*(file: File): FileInfo {.noNimScript.} = if file.isNil: raise newException(IOError, "File is nil") result = getFileInfo(file.getFileHandle()) -proc getFileInfo*(path: string, followSymlink = true): FileInfo = +proc getFileInfo*(path: string, followSymlink = true): FileInfo {.noNimScript.} = ## Retrieves file information for the file object pointed to by `path`. ## ## Due to intrinsic differences between operating systems, the information @@ -1714,7 +2393,7 @@ proc getFileInfo*(path: string, followSymlink = true): FileInfo = raiseOSError(osLastError()) rawToFormalFileInfo(rawInfo, path, result) -proc isHidden*(path: string): bool = +proc isHidden*(path: string): bool {.noNimScript.} = ## Determines whether ``path`` is hidden or not, using this ## reference https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory ## @@ -1744,7 +2423,7 @@ proc isHidden*(path: string): bool = {.pop.} -proc setLastModificationTime*(file: string, t: times.Time) = +proc setLastModificationTime*(file: string, t: times.Time) {.noNimScript.} = ## Sets the `file`'s last modification time. `OSError` is raised in case of ## an error. when defined(posix): @@ -1760,3 +2439,37 @@ proc setLastModificationTime*(file: string, t: times.Time) = let res = setFileTime(h, nil, nil, ft.addr) discard h.closeHandle if res == 0'i32: raiseOSError(osLastError()) + +when isMainModule: + assert quoteShellWindows("aaa") == "aaa" + assert quoteShellWindows("aaa\"") == "aaa\\\"" + assert quoteShellWindows("") == "\"\"" + + assert quoteShellPosix("aaa") == "aaa" + assert quoteShellPosix("aaa a") == "'aaa a'" + assert quoteShellPosix("") == "''" + assert quoteShellPosix("a'a") == "'a'\"'\"'a'" + + when defined(posix): + assert quoteShell("") == "''" + + block normalizePathEndTest: + # handle edge cases correctly: shouldn't affect whether path is + # absolute/relative + doAssert "".normalizePathEnd(true) == "" + doAssert "".normalizePathEnd(false) == "" + doAssert "/".normalizePathEnd(true) == $DirSep + doAssert "/".normalizePathEnd(false) == $DirSep + + when defined(posix): + doAssert "//".normalizePathEnd(false) == "/" + doAssert "foo.bar//".normalizePathEnd == "foo.bar" + doAssert "bar//".normalizePathEnd(trailingSep = true) == "bar/" + when defined(Windows): + doAssert r"C:\foo\\".normalizePathEnd == r"C:\foo" + doAssert r"C:\foo".normalizePathEnd(trailingSep = true) == r"C:\foo\" + # this one is controversial: we could argue for returning `D:\` instead, + # but this is simplest. + doAssert r"D:\".normalizePathEnd == r"D:" + doAssert r"E:/".normalizePathEnd(trailingSep = true) == r"E:\" + doAssert "/".normalizePathEnd == r"\" diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim index 7f7f9a425..34d452fd2 100644 --- a/lib/pure/ospaths.nim +++ b/lib/pure/ospaths.nim @@ -7,707 +7,15 @@ # distribution, for details about the copyright. # -# Forwarded by the ``os`` module but a module in its own right for NimScript -# support. - -include "system/inclrtl" - -import strutils - -type - ReadEnvEffect* = object of ReadIOEffect ## effect that denotes a read - ## from an environment variable - WriteEnvEffect* = object of WriteIOEffect ## effect that denotes a write - ## to an environment variable - - ReadDirEffect* = object of ReadIOEffect ## effect that denotes a read - ## operation from the directory - ## structure - WriteDirEffect* = object of WriteIOEffect ## effect that denotes a write - ## operation to - ## the directory structure - - OSErrorCode* = distinct int32 ## Specifies an OS Error Code. - -const - doslikeFileSystem* = defined(windows) or defined(OS2) or defined(DOS) - -when defined(Nimdoc): # only for proper documentation: - const - CurDir* = '.' - ## The constant string used by the operating system to refer to the - ## current directory. - ## - ## For example: '.' for POSIX or ':' for the classic Macintosh. - - ParDir* = ".." - ## The constant string used by the operating system to refer to the - ## parent directory. - ## - ## For example: ".." for POSIX or "::" for the classic Macintosh. - - DirSep* = '/' - ## The character used by the operating system to separate pathname - ## components, for example, '/' for POSIX or ':' for the classic - ## Macintosh. - - AltSep* = '/' - ## An alternative character used by the operating system to separate - ## pathname components, or the same as `DirSep` if only one separator - ## character exists. This is set to '/' on Windows systems - ## where `DirSep` is a backslash. - - PathSep* = ':' - ## The character conventionally used by the operating system to separate - ## search patch components (as in PATH), such as ':' for POSIX - ## or ';' for Windows. - - FileSystemCaseSensitive* = true - ## true if the file system is case sensitive, false otherwise. Used by - ## `cmpPaths` to compare filenames properly. - - ExeExt* = "" - ## The file extension of native executables. For example: - ## "" for POSIX, "exe" on Windows. - - ScriptExt* = "" - ## The file extension of a script file. For example: "" for POSIX, - ## "bat" on Windows. - - DynlibFormat* = "lib$1.so" - ## The format string to turn a filename into a `DLL`:idx: file (also - ## called `shared object`:idx: on some operating systems). - -elif defined(macos): - const - CurDir* = ':' - ParDir* = "::" - DirSep* = ':' - AltSep* = Dirsep - PathSep* = ',' - FileSystemCaseSensitive* = false - ExeExt* = "" - ScriptExt* = "" - DynlibFormat* = "$1.dylib" - - # MacOS paths - # =========== - # MacOS directory separator is a colon ":" which is the only character not - # allowed in filenames. - # - # A path containing no colon or which begins with a colon is a partial - # path. - # E.g. ":kalle:petter" ":kalle" "kalle" - # - # All other paths are full (absolute) paths. E.g. "HD:kalle:" "HD:" - # When generating paths, one is safe if one ensures that all partial paths - # begin with a colon, and all full paths end with a colon. - # In full paths the first name (e g HD above) is the name of a mounted - # volume. - # These names are not unique, because, for instance, two diskettes with the - # same names could be inserted. This means that paths on MacOS are not - # 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 - # grandparent etc. -elif doslikeFileSystem: - const - CurDir* = '.' - ParDir* = ".." - DirSep* = '\\' # seperator within paths - AltSep* = '/' - PathSep* = ';' # seperator between paths - FileSystemCaseSensitive* = false - ExeExt* = "exe" - ScriptExt* = "bat" - DynlibFormat* = "$1.dll" -elif defined(PalmOS) or defined(MorphOS): - const - DirSep* = '/' - AltSep* = Dirsep - PathSep* = ';' - ParDir* = ".." - FileSystemCaseSensitive* = false - ExeExt* = "" - ScriptExt* = "" - DynlibFormat* = "$1.prc" -elif defined(RISCOS): - const - DirSep* = '.' - AltSep* = '.' - ParDir* = ".." # is this correct? - PathSep* = ',' - FileSystemCaseSensitive* = true - ExeExt* = "" - ScriptExt* = "" - DynlibFormat* = "lib$1.so" -else: # UNIX-like operating system - const - CurDir* = '.' - ParDir* = ".." - DirSep* = '/' - AltSep* = DirSep - PathSep* = ':' - FileSystemCaseSensitive* = when defined(macosx): false else: true - ExeExt* = "" - ScriptExt* = "" - DynlibFormat* = when defined(macosx): "lib$1.dylib" else: "lib$1.so" - -const - ExtSep* = '.' - ## The character which separates the base filename from the extension; - ## for example, the '.' in ``os.nim``. - -proc normalizePathEnd(path: var string, trailingSep = false) = - ## ensures ``path`` has exactly 0 or 1 trailing `DirSep`, depending on - ## ``trailingSep``, and taking care of edge cases: it preservers whether - ## a path is absolute or relative, and makes sure trailing sep is `DirSep`, - ## not `AltSep`. - if path.len == 0: return - var i = path.len - while i >= 1 and path[i-1] in {DirSep, AltSep}: dec(i) - if trailingSep: - # foo// => foo - path.setLen(i) - # foo => foo/ - path.add DirSep - elif i>0: - # foo// => foo - path.setLen(i) - else: - # // => / (empty case was already taken care of) - path = $DirSep - -proc normalizePathEnd(path: string, trailingSep = false): string = - result = path - result.normalizePathEnd(trailingSep) - -proc joinPath*(head, tail: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Joins two directory names to one. - ## - ## For example on Unix: - ## - ## .. code-block:: nim - ## joinPath("usr", "lib") - ## - ## results in: - ## - ## .. code-block:: nim - ## "usr/lib" - ## - ## If head is the empty string, tail is returned. If tail is the empty - ## string, head is returned with a trailing path separator. If tail starts - ## with a path separator it will be removed when concatenated to head. Other - ## path separators not located on boundaries won't be modified. More - ## examples on Unix: - ## - ## .. code-block:: nim - ## assert joinPath("usr", "") == "usr/" - ## assert joinPath("", "lib") == "lib" - ## assert joinPath("", "/lib") == "/lib" - ## assert joinPath("usr/", "/lib") == "usr/lib" - if len(head) == 0: - result = tail - elif head[len(head)-1] in {DirSep, AltSep}: - if tail.len > 0 and tail[0] in {DirSep, AltSep}: - result = head & substr(tail, 1) - else: - result = head & tail - else: - if tail.len > 0 and tail[0] in {DirSep, AltSep}: - result = head & tail - else: - result = head & DirSep & tail - -proc joinPath*(parts: varargs[string]): string {.noSideEffect, - rtl, extern: "nos$1OpenArray".} = - ## 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 - ## will assert in debug builds and crash on release builds. - result = parts[0] - for i in 1..high(parts): - result = joinPath(result, parts[i]) - -proc `/` * (head, tail: string): string {.noSideEffect.} = - ## The same as ``joinPath(head, tail)`` - ## - ## Here are some examples for Unix: - ## - ## .. code-block:: nim - ## assert "usr" / "" == "usr/" - ## assert "" / "lib" == "lib" - ## assert "" / "/lib" == "/lib" - ## assert "usr/" / "/lib" == "usr/lib" - return joinPath(head, tail) - -proc splitPath*(path: string): tuple[head, tail: string] {. - noSideEffect, rtl, extern: "nos$1".} = - ## Splits a directory into (head, tail), so that - ## ``head / tail == path`` (except for edge cases like "/usr"). - ## - ## Examples: - ## - ## .. code-block:: nim - ## splitPath("usr/local/bin") -> ("usr/local", "bin") - ## splitPath("usr/local/bin/") -> ("usr/local/bin", "") - ## splitPath("bin") -> ("", "bin") - ## splitPath("/bin") -> ("", "bin") - ## splitPath("") -> ("", "") - var sepPos = -1 - for i in countdown(len(path)-1, 0): - if path[i] in {DirSep, AltSep}: - sepPos = i - break - if sepPos >= 0: - result.head = substr(path, 0, sepPos-1) - result.tail = substr(path, sepPos+1) - else: - result.head = "" - result.tail = path - -proc parentDirPos(path: string): int = - var q = 1 - if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2 - for i in countdown(len(path)-q, 0): - if path[i] in {DirSep, AltSep}: return i - result = -1 - -proc parentDir*(path: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Returns the parent directory of `path`. - ## - ## This is the same as ``splitPath(path).head`` when ``path`` doesn't end - ## in a dir separator. - ## The remainder can be obtained with ``lastPathPart(path)`` - runnableExamples: - doAssert parentDir("") == "" - when defined(posix): - doAssert parentDir("/usr/local/bin") == "/usr/local" - doAssert parentDir("foo/bar/") == "foo" - - let sepPos = parentDirPos(path) - if sepPos >= 0: - result = substr(path, 0, sepPos-1) - else: - result = "" - -proc tailDir*(path: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Returns the tail part of `path`.. - ## - ## | Example: ``tailDir("/usr/local/bin") == "local/bin"``. - ## | Example: ``tailDir("usr/local/bin/") == "local/bin"``. - ## | Example: ``tailDir("bin") == ""``. - var q = 1 - if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2 - for i in 0..len(path)-q: - if path[i] in {DirSep, AltSep}: - return substr(path, i+1) - result = "" - -proc isRootDir*(path: string): bool {. - noSideEffect, rtl, extern: "nos$1".} = - ## Checks whether a given `path` is a root directory - result = parentDirPos(path) < 0 - -iterator parentDirs*(path: string, fromRoot=false, inclusive=true): string = - ## Walks over all parent directories of a given `path` - ## - ## If `fromRoot` is set, the traversal will start from the file system root - ## diretory. If `inclusive` is set, the original argument will be included - ## in the traversal. - ## - ## Relative paths won't be expanded by this proc. Instead, it will traverse - ## only the directories appearing in the relative path. - if not fromRoot: - var current = path - if inclusive: yield path - while true: - if current.isRootDir: break - current = current.parentDir - yield current - else: - for i in countup(0, path.len - 2): # ignore the last / - # deal with non-normalized paths such as /foo//bar//baz - if path[i] in {DirSep, AltSep} and - (i == 0 or path[i-1] notin {DirSep, AltSep}): - yield path.substr(0, i) - - if inclusive: yield path - -proc `/../`*(head, tail: string): string {.noSideEffect.} = - ## The same as ``parentDir(head) / tail`` unless there is no parent - ## directory. Then ``head / tail`` is performed instead. - let sepPos = parentDirPos(head) - if sepPos >= 0: - result = substr(head, 0, sepPos-1) / tail - else: - result = head / tail - -proc normExt(ext: string): string = - if ext == "" or ext[0] == ExtSep: result = ext # no copy needed here - else: result = ExtSep & ext - -proc searchExtPos*(path: string): int = - ## Returns index of the '.' char in `path` if it signifies the beginning - ## of extension. Returns -1 otherwise. - # BUGFIX: do not search until 0! .DS_Store is no file extension! - result = -1 - for i in countdown(len(path)-1, 1): - if path[i] == ExtSep: - result = i - break - elif path[i] in {DirSep, AltSep}: - break # do not skip over path - -proc splitFile*(path: string): tuple[dir, name, ext: string] {. - noSideEffect, rtl, extern: "nos$1".} = - ## Splits a filename into (dir, filename, extension). - ## `dir` does not end in `DirSep`. - ## `extension` includes the leading dot. - ## - ## Example: - ## - ## .. code-block:: nim - ## var (dir, name, ext) = splitFile("usr/local/nimc.html") - ## assert dir == "usr/local" - ## assert name == "nimc" - ## assert ext == ".html" - ## - ## If `path` has no extension, `ext` is the empty string. - ## If `path` has no directory component, `dir` is the empty string. - ## If `path` has no filename component, `name` and `ext` are empty strings. - if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: - result = (path, "", "") - else: - var sepPos = -1 - var dotPos = path.len - for i in countdown(len(path)-1, 0): - if path[i] == ExtSep: - if dotPos == path.len and i > 0 and - path[i-1] notin {DirSep, AltSep}: dotPos = i - elif path[i] in {DirSep, AltSep}: - sepPos = i - break - result.dir = substr(path, 0, sepPos-1) - result.name = substr(path, sepPos+1, dotPos-1) - result.ext = substr(path, dotPos) - -proc extractFilename*(path: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Extracts the filename of a given `path`. This is the same as - ## ``name & ext`` from ``splitFile(path)``. See also ``lastPathPart``. - runnableExamples: - when defined(posix): - doAssert extractFilename("foo/bar/") == "" - doAssert extractFilename("foo/bar") == "bar" - if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: - result = "" - else: - result = splitPath(path).tail - -proc lastPathPart*(path: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## like ``extractFilename``, but ignores trailing dir separator; aka: `baseName`:idx: - ## in some other languages. - runnableExamples: - when defined(posix): - doAssert lastPathPart("foo/bar/") == "bar" - let path = path.normalizePathEnd(trailingSep = false) - result = extractFilename(path) - -proc changeFileExt*(filename, ext: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Changes the file extension to `ext`. - ## - ## If the `filename` has no extension, `ext` will be added. - ## If `ext` == "" then any extension is removed. - ## `Ext` should be given without the leading '.', because some - ## filesystems may use a different character. (Although I know - ## of none such beast.) - var extPos = searchExtPos(filename) - if extPos < 0: result = filename & normExt(ext) - else: result = substr(filename, 0, extPos-1) & normExt(ext) - -proc addFileExt*(filename, ext: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Adds the file extension `ext` to `filename`, unless - ## `filename` already has an extension. - ## - ## `Ext` should be given without the leading '.', because some - ## filesystems may use a different character. - ## (Although I know of none such beast.) - var extPos = searchExtPos(filename) - if extPos < 0: result = filename & normExt(ext) - else: result = filename - -proc cmpPaths*(pathA, pathB: string): int {. - noSideEffect, rtl, extern: "nos$1".} = - ## Compares two paths. - ## - ## On a case-sensitive filesystem this is done - ## case-sensitively otherwise case-insensitively. Returns: - ## - ## | 0 iff pathA == pathB - ## | < 0 iff pathA < pathB - ## | > 0 iff pathA > pathB - runnableExamples: - when defined(macosx): - doAssert cmpPaths("foo", "Foo") == 0 - elif defined(posix): - doAssert cmpPaths("foo", "Foo") > 0 - if FileSystemCaseSensitive: - result = cmp(pathA, pathB) - else: - when defined(nimscript): - result = cmpic(pathA, pathB) - elif defined(nimdoc): discard - else: - result = cmpIgnoreCase(pathA, pathB) - -proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} = - ## Checks whether a given `path` is absolute. - ## - ## On Windows, network paths are considered absolute too. - runnableExamples: - doAssert(not "".isAbsolute) - doAssert(not ".".isAbsolute) - when defined(posix): - doAssert "/".isAbsolute - doAssert(not "a/".isAbsolute) - - if len(path) == 0: return false - - when doslikeFileSystem: - var len = len(path) - result = (path[0] in {'/', '\\'}) or - (len > 1 and path[0] in {'a'..'z', 'A'..'Z'} and path[1] == ':') - elif defined(macos): - # according to https://perldoc.perl.org/File/Spec/Mac.html `:a` is a relative path - result = path[0] != ':' - elif defined(RISCOS): - result = path[0] == '$' - elif defined(posix): - result = path[0] == '/' - -proc unixToNativePath*(path: string, drive=""): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Converts an UNIX-like path to a native one. - ## - ## On an UNIX system this does nothing. Else it converts - ## '/', '.', '..' to the appropriate things. - ## - ## On systems with a concept of "drives", `drive` is used to determine - ## which drive label to use during absolute path conversion. - ## `drive` defaults to the drive of the current working directory, and is - ## ignored on systems that do not have a concept of "drives". - - when defined(unix): - result = path - else: - if path.len == 0: - return "" - - var start: int - if path[0] == '/': - # an absolute path - when doslikeFileSystem: - if drive != "": - result = drive & ":" & DirSep - else: - result = $DirSep - elif defined(macos): - result = "" # must not start with ':' - else: - result = $DirSep - start = 1 - elif path[0] == '.' and (path.len == 1 or path[1] == '/'): - # current directory - result = $CurDir - start = when doslikeFileSystem: 1 else: 2 - else: - result = "" - start = 0 - - var i = start - while i < len(path): # ../../../ --> :::: - if i+2 < path.len and path[i] == '.' and path[i+1] == '.' and path[i+2] == '/': - # parent directory - when defined(macos): - if result[high(result)] == ':': - add result, ':' - else: - add result, ParDir - else: - add result, ParDir & DirSep - inc(i, 3) - elif path[i] == '/': - add result, DirSep - inc(i) - else: - add result, path[i] - inc(i) - -include "includes/oserr" -when not defined(nimscript): - include "includes/osenv" - -proc getHomeDir*(): string {.rtl, extern: "nos$1", - tags: [ReadEnvEffect, ReadIOEffect].} = - ## Returns the home directory of the current user. - ## - ## This proc is wrapped by the expandTilde proc for the convenience of - ## processing paths coming from user configuration files. - when defined(windows): return string(getEnv("USERPROFILE")) & "\\" - else: return string(getEnv("HOME")) & "/" - -proc getConfigDir*(): string {.rtl, extern: "nos$1", - tags: [ReadEnvEffect, ReadIOEffect].} = - ## Returns the config directory of the current user for applications. - ## - ## On non-Windows OSs, this proc conforms to the XDG Base Directory - ## spec. Thus, this proc returns the value of the XDG_CONFIG_HOME environment - ## variable if it is set, and returns the default configuration directory, - ## "~/.config/", otherwise. - ## - ## An OS-dependent trailing slash is always present at the end of the - ## returned string; `\` on Windows and `/` on all other OSs. - when defined(windows): - result = getEnv("APPDATA").string - else: - result = getEnv("XDG_CONFIG_HOME", getEnv("HOME").string / ".config").string - result.normalizePathEnd(trailingSep = true) - -proc getTempDir*(): string {.rtl, extern: "nos$1", - tags: [ReadEnvEffect, ReadIOEffect].} = - ## Returns the temporary directory of the current user for applications to - ## save temporary files in. - ## - ## **Please do not use this**: On Android, it currently - ## returns ``getHomeDir()``, and on other Unix based systems it can cause - ## security problems too. That said, you can override this implementation - ## by adding ``-d:tempDir=mytempname`` to your compiler invokation. - when defined(tempDir): - const tempDir {.strdefine.}: string = nil - return tempDir - elif defined(windows): return string(getEnv("TEMP")) & "\\" - elif defined(android): return getHomeDir() - else: return "/tmp/" - -proc expandTilde*(path: string): string {. - tags: [ReadEnvEffect, ReadIOEffect].} = - ## Expands ``~`` or a path starting with ``~/`` to a full path, replacing - ## ``~`` with ``getHomeDir()`` (otherwise returns ``path`` unmodified). - ## - ## Windows: this is still supported despite Windows platform not having this - ## convention; also, both ``~/`` and ``~\`` are handled. - runnableExamples: - doAssert expandTilde("~" / "appname.cfg") == getHomeDir() / "appname.cfg" - if len(path) == 0 or path[0] != '~': - result = path - elif len(path) == 1: - result = getHomeDir() - elif (path[1] in {DirSep, AltSep}): - result = getHomeDir() / path.substr(2) - else: - # TODO: handle `~bob` and `~bob/` which means home of bob - result = path - -# TODO: consider whether quoteShellPosix, quoteShellWindows, quoteShell, quoteShellCommand -# belong in `strutils` instead; they are not specific to paths -proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = - ## Quote s, so it can be safely passed to Windows API. - ## Based on Python's subprocess.list2cmdline - ## See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx - let needQuote = {' ', '\t'} in s or s.len == 0 - - result = "" - var backslashBuff = "" - if needQuote: - result.add("\"") - - for c in s: - if c == '\\': - backslashBuff.add(c) - elif c == '\"': - result.add(backslashBuff) - result.add(backslashBuff) - backslashBuff.setLen(0) - result.add("\\\"") - else: - if backslashBuff.len != 0: - result.add(backslashBuff) - backslashBuff.setLen(0) - result.add(c) - - if needQuote: - result.add("\"") - -proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = - ## Quote ``s``, so it can be safely passed to POSIX shell. - ## Based on Python's pipes.quote - const safeUnixChars = {'%', '+', '-', '.', '/', '_', ':', '=', '@', - '0'..'9', 'A'..'Z', 'a'..'z'} - if s.len == 0: - return "''" - - let safe = s.allCharsInSet(safeUnixChars) - - if safe: - return s - else: - return "'" & s.replace("'", "'\"'\"'") & "'" - -when defined(windows) or defined(posix) or defined(nintendoswitch): - proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = - ## Quote ``s``, so it can be safely passed to shell. - when defined(windows): - return quoteShellWindows(s) - else: - return quoteShellPosix(s) - - proc quoteShellCommand*(args: openArray[string]): string = - ## Concatenates and quotes shell arguments `args` - runnableExamples: - when defined(posix): - assert quoteShellCommand(["aaa", "", "c d"]) == "aaa '' 'c d'" - when defined(windows): - assert quoteShellCommand(["aaa", "", "c d"]) == "aaa \"\" \"c d\"" - # can't use `map` pending https://github.com/nim-lang/Nim/issues/8303 - for i in 0..<args.len: - if i > 0: result.add " " - result.add quoteShell(args[i]) - -when isMainModule: - assert quoteShellWindows("aaa") == "aaa" - assert quoteShellWindows("aaa\"") == "aaa\\\"" - assert quoteShellWindows("") == "\"\"" - - assert quoteShellPosix("aaa") == "aaa" - assert quoteShellPosix("aaa a") == "'aaa a'" - assert quoteShellPosix("") == "''" - assert quoteShellPosix("a'a") == "'a'\"'\"'a'" - - when defined(posix): - assert quoteShell("") == "''" - - block normalizePathEndTest: - # handle edge cases correctly: shouldn't affect whether path is - # absolute/relative - doAssert "".normalizePathEnd(true) == "" - doAssert "".normalizePathEnd(false) == "" - doAssert "/".normalizePathEnd(true) == $DirSep - doAssert "/".normalizePathEnd(false) == $DirSep - - when defined(posix): - doAssert "//".normalizePathEnd(false) == "/" - doAssert "foo.bar//".normalizePathEnd == "foo.bar" - doAssert "bar//".normalizePathEnd(trailingSep = true) == "bar/" - when defined(Windows): - doAssert r"C:\foo\\".normalizePathEnd == r"C:\foo" - doAssert r"C:\foo".normalizePathEnd(trailingSep = true) == r"C:\foo\" - # this one is controversial: we could argue for returning `D:\` instead, - # but this is simplest. - doAssert r"D:\".normalizePathEnd == r"D:" - doAssert r"E:/".normalizePathEnd(trailingSep = true) == r"E:\" - doAssert "/".normalizePathEnd == r"\" +## This module is deprecated, ``import os`` instead. +{.deprecated: "import os.nim instead".} + +import os +export ReadEnvEffect, WriteEnvEffect, ReadDirEffect, WriteDirEffect, OSErrorCode, + doslikeFileSystem, CurDir, ParDir, DirSep, AltSep, PathSep, FileSystemCaseSensitive, + ExeExt, ScriptExt, DynlibFormat, ExtSep, joinPath, `/`, splitPath, parentDir, + tailDir, isRootDir, parentDirs, `/../`, searchExtPos, splitFile, extractFilename, + lastPathPart, changeFileExt, addFileExt, cmpPaths, isAbsolute, unixToNativePath, + `==`, `$`, osErrorMsg, raiseOSError, osLastError, getEnv, existsEnv, putEnv, + getHomeDir, getConfigDir, getTempDir, expandTilde, quoteShellWindows, + quoteShellPosix, quoteShell, quoteShellCommand diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index dfea4213d..879a2d3a7 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -15,7 +15,7 @@ include "system/inclrtl" import strutils, os, strtabs, streams, cpuinfo -from ospaths import quoteShell, quoteShellWindows, quoteShellPosix +#from ospaths import quoteShell, quoteShellWindows, quoteShellPosix export quoteShell, quoteShellWindows, quoteShellPosix when defined(windows): |