diff options
-rw-r--r-- | compiler/main.nim | 4 | ||||
-rw-r--r-- | compiler/scriptconfig.nim | 13 | ||||
-rw-r--r-- | lib/pure/os.nim | 526 | ||||
-rw-r--r-- | lib/pure/ospaths.nim | 560 | ||||
-rw-r--r-- | lib/pure/osproc.nim | 77 | ||||
-rw-r--r-- | lib/system/nimscript.nim | 27 | ||||
-rw-r--r-- | todo.txt | 2 |
7 files changed, 644 insertions, 565 deletions
diff --git a/compiler/main.nim b/compiler/main.nim index 7c043eb72..3bb6fc343 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -355,7 +355,9 @@ proc mainCommand* = gGlobalOptions.incl(optCaasEnabled) msgs.gErrorMax = high(int) # do not stop after first error serve(mainCommand) - of "nop", "help": discard + of "nop", "help": + # prevent the "success" message: + gCmd = cmdDump else: rawMessage(errInvalidCommandX, command) diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim index 148a382c2..115ce940d 100644 --- a/compiler/scriptconfig.nim +++ b/compiler/scriptconfig.nim @@ -68,9 +68,20 @@ proc setupVM*(module: PSym; scriptName: string): PEvalContext = setResult(a, os.getCurrentDir()) cbos moveFile: os.moveFile(getString(a, 0), getString(a, 1)) + cbos copyFile: + os.copyFile(getString(a, 0), getString(a, 1)) cbos getLastModificationTime: setResult(a, toSeconds(getLastModificationTime(getString(a, 0)))) + cbconf getEnv: + setResult(a, os.getEnv(a.getString 0)) + cbconf existsEnv: + setResult(a, os.existsEnv(a.getString 0)) + cbconf dirExists: + setResult(a, os.dirExists(a.getString 0)) + cbconf fileExists: + setResult(a, os.fileExists(a.getString 0)) + cbconf thisDir: setResult(a, vthisDir) cbconf put: @@ -87,6 +98,8 @@ proc setupVM*(module: PSym; scriptName: string): PEvalContext = setResult(a, os.paramCount()) cbconf cmpIgnoreStyle: setResult(a, strutils.cmpIgnoreStyle(a.getString 0, a.getString 1)) + cbconf cmpIgnoreCase: + setResult(a, strutils.cmpIgnoreCase(a.getString 0, a.getString 1)) cbconf setCommand: options.command = a.getString 0 cbconf getCommand: diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 3d592a526..f413371cb 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -27,147 +27,7 @@ else: {.error: "OS module not ported to your operating system!".} include "system/ansi_c" - -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 write - ## operation to 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. - -{.deprecated: [FReadEnv: ReadEnvEffect, FWriteEnv: WriteEnvEffect, - FReadDir: ReadDirEffect, - FWriteDir: WriteDirEffect, - TOSErrorCode: OSErrorCode -].} -const - doslike = defined(windows) or defined(OS2) or defined(DOS) - # DOS-like filesystem - -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 doslike: - 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* = true - ExeExt* = "" - ScriptExt* = "" - DynlibFormat* = when defined(macosx): "lib$1.dylib" else: "lib$1.so" +include ospaths when defined(posix): when NoFakeVars: @@ -177,11 +37,6 @@ when defined(posix): var pathMax {.importc: "PATH_MAX", header: "<stdlib.h>".}: cint -const - ExtSep* = '.' - ## The character which separates the base filename from the extension; - ## for example, the '.' in ``os.nim``. - proc osErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} = ## Retrieves the operating system's error flag, ``errno``. ## On Windows ``GetLastError`` is checked before ``errno``. @@ -300,61 +155,6 @@ proc osLastError*(): OSErrorCode = result = OSErrorCode(errno) {.pop.} -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: - var start: int - if path[0] == '/': - # an absolute path - when doslike: - 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[1] == '/': - # current directory - result = $CurDir - start = 2 - else: - result = "" - start = 0 - - var i = start - while i < len(path): # ../../../ --> :::: - if 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) - when defined(windows): when useWinUnicode: template wrapUnary(varname, winApiProc, arg: expr) {.immediate.} = @@ -518,220 +318,6 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [].} = else: if chdir(newDir) != 0'i32: raiseOSError(osLastError()) -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[0] in {DirSep, AltSep}: - result = head & substr(tail, 1) - else: - result = head & tail - else: - if 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 often the same as the ``head`` result of ``splitPath``. - ## If there is no parent, "" is returned. - ## | Example: ``parentDir("/usr/local/bin") == "/usr/local"``. - ## | Example: ``parentDir("/usr/local/bin/") == "/usr/local"``. - 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(s: string): int = - # BUGFIX: do not search until 0! .DS_Store is no file extension! - result = -1 - for i in countdown(len(s)-1, 1): - if s[i] == ExtSep: - result = i - break - elif s[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)``. - if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: - result = "" - else: - result = splitPath(path).tail - proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", tags: [ReadDirEffect].} = ## Returns the full path of `filename`, raises OSError in case of an error. @@ -757,61 +343,6 @@ proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", if r.isNil: raiseOSError(osLastError()) setLen(result, c_strlen(result)) -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 - if FileSystemCaseSensitive: - result = cmp(pathA, pathB) - 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. - when doslike: - var len = len(path) - result = (len > 1 and path[0] in {'/', '\\'}) or - (len > 2 and path[0] in Letters and path[1] == ':') - elif defined(macos): - result = path.len > 0 and path[0] != ':' - elif defined(RISCOS): - result = path[0] == '$' - elif defined(posix): - result = path[0] == '/' - when defined(Windows): proc openHandle(path: string, followSymlink=true): Handle = var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL @@ -1649,24 +1180,7 @@ proc exclFilePermissions*(filename: string, ## setFilePermissions(filename, getFilePermissions(filename)-permissions) setFilePermissions(filename, getFilePermissions(filename)-permissions) -proc getHomeDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect].} = - ## 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].} = - ## Returns the config directory of the current user for applications. - when defined(windows): return string(getEnv("APPDATA")) & "\\" - else: return string(getEnv("HOME")) & "/.config/" - -proc getTempDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect].} = - ## Returns the temporary directory of the current user for applications to - ## save temporary files in. - when defined(windows): return string(getEnv("TEMP")) & "\\" - else: return "/tmp/" +include ospaths proc expandSymlink*(symlinkPath: string): string = ## Returns a string representing the path to which the symbolic link points. @@ -1907,42 +1421,6 @@ proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1", close(f) else: raiseOSError(osLastError()) -proc expandTilde*(path: string): string {.tags: [ReadEnvEffect].} = - ## Expands a path starting with ``~/`` to a full path. - ## - ## If `path` starts with the tilde character and is followed by `/` or `\\` - ## this proc will return the reminder of the path appended to the result of - ## the getHomeDir() proc, otherwise the input path will be returned without - ## modification. - ## - ## The behaviour of this proc is the same on the Windows platform despite not - ## having this convention. Example: - ## - ## .. code-block:: nim - ## let configFile = expandTilde("~" / "appname.cfg") - ## echo configFile - ## # --> C:\Users\amber\appname.cfg - if len(path) > 1 and path[0] == '~' and (path[1] == '/' or path[1] == '\\'): - result = getHomeDir() / path[2..len(path)-1] - else: - result = path - -proc findExe*(exe: string): string {.tags: [ReadDirEffect, ReadEnvEffect].} = - ## 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. On DOS-like platforms, `exe` - ## is added the `ExeExt <#ExeExt>`_ file extension if it has none. - result = addFileExt(exe, os.ExeExt) - if existsFile(result): return - var path = string(os.getEnv("PATH")) - for candidate in split(path, PathSep): - when defined(windows): - var x = candidate / result - else: - var x = expandTilde(candidate) / result - if existsFile(x): return x - result = "" - when defined(Windows): type DeviceId* = int32 diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim new file mode 100644 index 000000000..55962a6a5 --- /dev/null +++ b/lib/pure/ospaths.nim @@ -0,0 +1,560 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2015 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +# Included by the ``os`` module but a module in its own right for NimScript +# support. + +when defined(nimscript): + {.pragma: rtl.} + {.push hint[ConvFromXtoItselfNotNeeded]:off.} + +when not declared(getEnv) or defined(nimscript): + 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 write + ## operation to 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. + + {.deprecated: [FReadEnv: ReadEnvEffect, FWriteEnv: WriteEnvEffect, + FReadDir: ReadDirEffect, + FWriteDir: WriteDirEffect, + TOSErrorCode: OSErrorCode + ].} + const + doslike = defined(windows) or defined(OS2) or defined(DOS) + # DOS-like filesystem + + 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 doslike: + 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* = 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 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[0] in {DirSep, AltSep}: + result = head & substr(tail, 1) + else: + result = head & tail + else: + if 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 often the same as the ``head`` result of ``splitPath``. + ## If there is no parent, "" is returned. + ## | Example: ``parentDir("/usr/local/bin") == "/usr/local"``. + ## | Example: ``parentDir("/usr/local/bin/") == "/usr/local"``. + 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(s: string): int = + # BUGFIX: do not search until 0! .DS_Store is no file extension! + result = -1 + for i in countdown(len(s)-1, 1): + if s[i] == ExtSep: + result = i + break + elif s[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)``. + if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: + result = "" + else: + result = splitPath(path).tail + + + 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 + if FileSystemCaseSensitive: + result = cmp(pathA, pathB) + else: + when defined(nimscript): + result = cmpic(pathA, pathB) + 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. + when doslike: + var len = len(path) + result = (len > 1 and path[0] in {'/', '\\'}) or + (len > 2 and path[0] in {'a'..'z', 'A'..'Z'} and path[1] == ':') + elif defined(macos): + result = path.len > 0 and 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: + var start: int + if path[0] == '/': + # an absolute path + when doslike: + 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[1] == '/': + # current directory + result = $CurDir + start = 2 + else: + result = "" + start = 0 + + var i = start + while i < len(path): # ../../../ --> :::: + if 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) + +when declared(getEnv) or defined(nimscript): + proc getHomeDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect].} = + ## 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].} = + ## Returns the config directory of the current user for applications. + when defined(windows): return string(getEnv("APPDATA")) & "\\" + else: return string(getEnv("HOME")) & "/.config/" + + proc getTempDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect].} = + ## Returns the temporary directory of the current user for applications to + ## save temporary files in. + when defined(windows): return string(getEnv("TEMP")) & "\\" + else: return "/tmp/" + + proc expandTilde*(path: string): string {.tags: [ReadEnvEffect].} = + ## Expands a path starting with ``~/`` to a full path. + ## + ## If `path` starts with the tilde character and is followed by `/` or `\\` + ## this proc will return the reminder of the path appended to the result of + ## the getHomeDir() proc, otherwise the input path will be returned without + ## modification. + ## + ## The behaviour of this proc is the same on the Windows platform despite not + ## having this convention. Example: + ## + ## .. code-block:: nim + ## let configFile = expandTilde("~" / "appname.cfg") + ## echo configFile + ## # --> C:\Users\amber\appname.cfg + if len(path) > 1 and path[0] == '~' and (path[1] == '/' or path[1] == '\\'): + result = getHomeDir() / path.substr(2) + else: + 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) + + proc findExe*(exe: string): string {.tags: [ReadDirEffect, ReadEnvEffect].} = + ## 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. On DOS-like platforms, `exe` + ## is added the `ExeExt <#ExeExt>`_ file extension if it has none. + result = addFileExt(exe, ExeExt) + if existsFile(result): return + var path = string(getEnv("PATH")) + for candidate in split(path, PathSep): + when defined(windows): + var x = candidate / result + else: + var x = expandTilde(candidate) / result + if existsFile(x): return x + result = "" + +when defined(nimscript): + {.pop.} # hint[ConvFromXtoItselfNotNeeded]:off diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 81befccff..7431be702 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -614,11 +614,14 @@ elif not defined(useNimRtl): optionPoStdErrToStdOut: bool {.deprecated: [TStartProcessData: StartProcessData].} - when not defined(useFork): + const useProcessAuxSpawn = declared(posix_spawn) and not defined(useFork) and + not defined(useClone) and not defined(linux) + when useProcessAuxSpawn: proc startProcessAuxSpawn(data: StartProcessData): Pid {. tags: [ExecIOEffect, ReadEnvEffect], gcsafe.} - proc startProcessAuxFork(data: StartProcessData): Pid {. - tags: [ExecIOEffect, ReadEnvEffect], gcsafe.} + else: + proc startProcessAuxFork(data: StartProcessData): Pid {. + tags: [ExecIOEffect, ReadEnvEffect], gcsafe.} {.push stacktrace: off, profiler: off.} proc startProcessAfterFork(data: ptr StartProcessData) {. tags: [ExecIOEffect, ReadEnvEffect], cdecl, gcsafe.} @@ -674,9 +677,7 @@ elif not defined(useNimRtl): data.optionPoStdErrToStdOut = poStdErrToStdOut in options data.workingDir = workingDir - - when declared(posix_spawn) and not defined(useFork) and - not defined(useClone) and not defined(linux): + when useProcessAuxSpawn: pid = startProcessAuxSpawn(data) else: pid = startProcessAuxFork(data) @@ -706,7 +707,7 @@ elif not defined(useNimRtl): discard close(pStdin[readIdx]) discard close(pStdout[writeIdx]) - when not defined(useFork): + when useProcessAuxSpawn: proc startProcessAuxSpawn(data: StartProcessData): Pid = var attr: Tposix_spawnattr var fops: Tposix_spawn_file_actions @@ -752,43 +753,43 @@ elif not defined(useNimRtl): discard posix_spawnattr_destroy(attr) chck res return pid + else: + proc startProcessAuxFork(data: StartProcessData): Pid = + if pipe(data.pErrorPipe) != 0: + raiseOSError(osLastError()) - proc startProcessAuxFork(data: StartProcessData): Pid = - if pipe(data.pErrorPipe) != 0: - raiseOSError(osLastError()) + defer: + discard close(data.pErrorPipe[readIdx]) - defer: - discard close(data.pErrorPipe[readIdx]) + var pid: Pid + var dataCopy = data + + when defined(useClone): + const stackSize = 65536 + let stackEnd = cast[clong](alloc(stackSize)) + let stack = cast[pointer](stackEnd + stackSize) + let fn: pointer = startProcessAfterFork + pid = clone(fn, stack, + cint(CLONE_VM or CLONE_VFORK or SIGCHLD), + pointer(addr dataCopy), nil, nil, nil) + discard close(data.pErrorPipe[writeIdx]) + dealloc(stack) + else: + pid = fork() + if pid == 0: + startProcessAfterFork(addr(dataCopy)) + exitnow(1) - var pid: Pid - var dataCopy = data - - when defined(useClone): - const stackSize = 65536 - let stackEnd = cast[clong](alloc(stackSize)) - let stack = cast[pointer](stackEnd + stackSize) - let fn: pointer = startProcessAfterFork - pid = clone(fn, stack, - cint(CLONE_VM or CLONE_VFORK or SIGCHLD), - pointer(addr dataCopy), nil, nil, nil) discard close(data.pErrorPipe[writeIdx]) - dealloc(stack) - else: - pid = fork() - if pid == 0: - startProcessAfterFork(addr(dataCopy)) - exitnow(1) - - discard close(data.pErrorPipe[writeIdx]) - if pid < 0: raiseOSError(osLastError()) + if pid < 0: raiseOSError(osLastError()) - var error: cint - let sizeRead = read(data.pErrorPipe[readIdx], addr error, sizeof(error)) - if sizeRead == sizeof(error): - raiseOSError("Could not find command: '$1'. OS error: $2" % - [$data.sysCommand, $strerror(error)]) + var error: cint + let sizeRead = read(data.pErrorPipe[readIdx], addr error, sizeof(error)) + if sizeRead == sizeof(error): + raiseOSError("Could not find command: '$1'. OS error: $2" % + [$data.sysCommand, $strerror(error)]) - return pid + return pid {.push stacktrace: off, profiler: off.} proc startProcessFail(data: ptr StartProcessData) = diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim index a1a653cf5..38e1f81a7 100644 --- a/lib/system/nimscript.nim +++ b/lib/system/nimscript.nim @@ -19,6 +19,7 @@ proc listFiles*(dir: string): seq[string] = builtin proc removeDir(dir: string) = builtin proc removeFile(dir: string) = builtin proc moveFile(src, dest: string) = builtin +proc copyFile(src, dest: string) = builtin proc createDir(dir: string) = builtin proc getOsError: string = builtin proc setCurrentDir(dir: string) = builtin @@ -30,6 +31,25 @@ proc switch*(key: string, val="") = builtin proc getCommand*(): string = builtin proc setCommand*(cmd: string) = builtin proc cmpIgnoreStyle(a, b: string): int = builtin +proc cmpIgnoreCase(a, b: string): int = builtin + +proc cmpic*(a, b: string): int = cmpIgnoreCase(a, b) + +proc getEnv*(key: string): string = builtin +proc existsEnv*(key: string): bool = builtin +proc fileExists*(filename: string): bool = builtin +proc dirExists*(dir: string): bool = builtin + +proc existsFile*(filename: string): bool = fileExists(filename) +proc existsDir*(dir: string): bool = dirExists(dir) + +proc toExe*(filename: string): string = + ## On Windows adds ".exe" to `filename`, else returns `filename` unmodified. + (when defined(windows): filename & ".exe" else: filename) + +proc toDll*(filename: string): string = + ## On Windows adds ".dll" to `filename`, on Posix produces "lib$filename.so". + (when defined(windows): filename & ".dll" else: "lib" & filename & ".so") proc strip(s: string): string = var i = 0 @@ -79,6 +99,11 @@ proc mvFile*(`from`, to: string) {.raises: [OSError].} = moveFile `from`, to checkOsError() +proc cpFile*(`from`, to: string) {.raises: [OSError].} = + log "mvFile: " & `from` & ", " & to: + copyFile `from`, to + checkOsError() + proc exec*(command: string, input = "", cache = "") = ## Executes an external process. log "exec: " & command: @@ -120,7 +145,7 @@ template withDir*(dir: string; body: untyped): untyped = ## ## If you need a permanent change, use the `cd() <#cd>`_ proc. Usage example: ## - ## .. code-block:: nimrod + ## .. code-block:: nim ## withDir "foo": ## # inside foo ## #back to last dir diff --git a/todo.txt b/todo.txt index 82d1f32db..463ffc3b1 100644 --- a/todo.txt +++ b/todo.txt @@ -1,7 +1,7 @@ version 0.11.4 ============== -- make ``os`` and ``math`` work with NimScript +- document NimScript - document special cased varargs[untyped] and varargs[typed] - The remaining bugs of the lambda lifting pass that is responsible to enable |