summary refs log tree commit diff stats
path: root/nimlib/pure/os.nim
diff options
context:
space:
mode:
Diffstat (limited to 'nimlib/pure/os.nim')
-rwxr-xr-xnimlib/pure/os.nim1147
1 files changed, 1147 insertions, 0 deletions
diff --git a/nimlib/pure/os.nim b/nimlib/pure/os.nim
new file mode 100755
index 000000000..afa145e9f
--- /dev/null
+++ b/nimlib/pure/os.nim
@@ -0,0 +1,1147 @@
+#
+#
+#            Nimrod's Runtime Library
+#        (c) Copyright 2009 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module contains basic operating system facilities like
+## retrieving environment variables, reading command line arguments,
+## working with directories, running shell commands, etc.
+{.deadCodeElim: on.}
+
+{.push debugger: off.}
+
+import
+  strutils, times
+
+when defined(windows):
+  import winlean
+elif defined(posix): 
+  import posix
+else:
+  {.error: "OS module not ported to your operating system!".}
+
+include "system/ansi_c"
+
+# copied from excpt.nim, because I don't want to make this template public
+template newException(exceptn, message: expr): expr =
+  block: # open a new scope
+    var
+      e: ref exceptn
+    new(e)
+    e.msg = message
+    e
+
+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.
+
+elif defined(macos):
+  const
+    curdir* = ':'
+    pardir* = "::"
+    dirsep* = ':'
+    altsep* = dirsep
+    pathsep* = ','
+    FileSystemCaseSensitive* = false
+    ExeExt* = ""
+    ScriptExt* = ""
+
+  #  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"
+elif defined(PalmOS) or defined(MorphOS):
+  const
+    dirsep* = '/'
+    altsep* = dirsep
+    PathSep* = ';'
+    pardir* = ".."
+    FileSystemCaseSensitive* = false
+    ExeExt* = ""
+    ScriptExt* = ""
+elif defined(RISCOS):
+  const
+    dirsep* = '.'
+    altsep* = '.'
+    pardir* = ".." # is this correct?
+    pathSep* = ','
+    FileSystemCaseSensitive* = true
+    ExeExt* = ""
+    ScriptExt* = ""
+else: # UNIX-like operating system
+  const
+    curdir* = '.'
+    pardir* = ".."
+    dirsep* = '/'
+    altsep* = dirsep
+    pathSep* = ':'
+    FileSystemCaseSensitive* = true
+    ExeExt* = ""
+    ScriptExt* = ""
+
+const
+  ExtSep* = '.'
+    ## The character which separates the base filename from the extension;
+    ## for example, the '.' in ``os.nim``.
+
+# procs dealing with command line arguments:
+proc paramCount*(): int
+  ## Returns the number of command line arguments given to the
+  ## application.
+
+proc paramStr*(i: int): string
+  ## Returns the `i`-th command line arguments given to the
+  ## application.
+  ##
+  ## `i` should be in the range `1..paramCount()`, else
+  ## the `EOutOfIndex` exception is raised.
+
+proc OSError*(msg: string = "") {.noinline.} =
+  ## raises an EOS exception with the given message ``msg``.
+  ## If ``msg == ""``, the operating system's error flag
+  ## (``errno``) is converted to a readable error message. On Windows
+  ## ``GetLastError`` is checked before ``errno``.
+  ## If no error flag is set, the message ``unknown OS error`` is used.
+  if len(msg) == 0:
+    when defined(Windows):
+      var err = GetLastError()
+      if err != 0'i32:
+        # sigh, why is this is so difficult?
+        var msgbuf: cstring
+        if FormatMessageA(0x00000100 or 0x00001000 or 0x00000200,
+                          nil, err, 0, addr(msgbuf), 0, nil) != 0'i32:
+          var m = $msgbuf
+          if msgbuf != nil:
+            LocalFree(msgbuf)
+          raise newException(EOS, m)
+    if errno != 0'i32:
+      raise newException(EOS, $os.strerror(errno))
+    else:
+      raise newException(EOS, "unknown OS error")
+  else:
+    raise newException(EOS, msg)
+
+proc UnixToNativePath*(path: string): string {.noSideEffect.} =
+  ## Converts an UNIX-like path to a native one.
+  ##
+  ## On an UNIX system this does nothing. Else it converts
+  ## '/', '.', '..' to the appropriate things.
+  when defined(unix):
+    result = path
+  else:
+    var start: int
+    if path[0] == '/':
+      # an absolute path
+      when doslike:
+        result = r"C:\"
+      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)
+
+proc existsFile*(filename: string): bool =
+  ## Returns true if the file exists, false otherwise.
+  when defined(windows):
+    var a = GetFileAttributesA(filename)
+    if a != -1'i32:
+      result = (a and FILE_ATTRIBUTE_DIRECTORY) == 0'i32
+  else:
+    var res: TStat
+    return stat(filename, res) >= 0'i32 and S_ISREG(res.st_mode)
+
+proc existsDir*(dir: string): bool =
+  ## Returns true iff the directory `dir` exists. If `dir` is a file, false
+  ## is returned.
+  when defined(windows):
+    var a = GetFileAttributesA(dir)
+    if a != -1'i32:
+      result = (a and FILE_ATTRIBUTE_DIRECTORY) != 0'i32
+  else:
+    var res: TStat
+    return stat(dir, res) >= 0'i32 and S_ISDIR(res.st_mode)
+
+proc getLastModificationTime*(file: string): TTime =
+  ## Returns the `file`'s last modification time.
+  when defined(posix):
+    var res: TStat
+    if stat(file, res) < 0'i32: OSError()
+    return res.st_mtime
+  else:
+    var f: TWIN32_Find_Data
+    var h = findfirstFileA(file, f)
+    if h == -1'i32: OSError()
+    result = winTimeToUnixTime(rdFileTime(f.ftLastWriteTime))
+    findclose(h)
+
+proc getLastAccessTime*(file: string): TTime =
+  ## Returns the `file`'s last read or write access time.
+  when defined(posix):
+    var res: TStat
+    if stat(file, res) < 0'i32: OSError()
+    return res.st_atime
+  else:
+    var f: TWIN32_Find_Data
+    var h = findfirstFileA(file, f)
+    if h == -1'i32: OSError()
+    result = winTimeToUnixTime(rdFileTime(f.ftLastAccessTime))
+    findclose(h)
+
+proc getCreationTime*(file: string): TTime = 
+  ## Returns the `file`'s creation time.
+  when defined(posix):
+    var res: TStat
+    if stat(file, res) < 0'i32: OSError()
+    return res.st_ctime
+  else:
+    var f: TWIN32_Find_Data
+    var h = findfirstFileA(file, f)
+    if h == -1'i32: OSError()
+    result = winTimeToUnixTime(rdFileTime(f.ftCreationTime))
+    findclose(h)
+
+proc fileNewer*(a, b: string): bool =
+  ## Returns true if the file `a` is newer than file `b`, i.e. if `a`'s
+  ## modification time is later than `b`'s.
+  result = getLastModificationTime(a) - getLastModificationTime(b) > 0
+
+proc getCurrentDir*(): string =
+  ## Returns the current working directory.
+  const bufsize = 512 # should be enough
+  result = newString(bufsize)
+  when defined(windows):
+    var L = GetCurrentDirectoryA(bufsize, result)
+    if L == 0'i32: OSError()
+    setLen(result, L)
+  else:
+    if getcwd(result, bufsize) != nil:
+      setlen(result, c_strlen(result))
+    else:
+      OSError()
+
+proc setCurrentDir*(newDir: string) {.inline.} =
+  ## Sets the current working directory; `EOS` is raised if
+  ## `newDir` cannot been set.
+  when defined(Windows):
+    if SetCurrentDirectoryA(newDir) == 0'i32: OSError()
+  else:
+    if chdir(newDir) != 0'i32: OSError()
+
+proc JoinPath*(head, tail: string): string {.noSideEffect.} =
+  ## Joins two directory names to one.
+  ##
+  ## For example on Unix:
+  ##
+  ## ..code-block:: nimrod
+  ##   JoinPath("usr", "lib")
+  ##
+  ## results in:
+  ##
+  ## ..code-block:: nimrod
+  ##   "usr/lib"
+  ##
+  ## If head is the empty string, tail is returned.
+  ## If tail is the empty string, head is returned.
+  if len(head) == 0:
+    result = tail
+  elif head[len(head)-1] in {DirSep, AltSep}:
+    if tail[0] in {DirSep, AltSep}:
+      result = head & copy(tail, 1)
+    else:
+      result = head & tail
+  else:
+    if tail[0] in {DirSep, AltSep}:
+      result = head & tail
+    else:
+      result = head & DirSep & tail
+
+proc JoinPath*(parts: openarray[string]): string {.noSideEffect.} =
+  ## The same as `JoinPath(head, tail)`, but works with any number
+  ## of directory parts.
+  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)``
+  return joinPath(head, tail)
+
+proc SplitPath*(path: string, head, tail: var string) {.noSideEffect,
+                                                        deprecated.} =
+  ## **Deprecated since version 0.8.2**: use the version that returns a tuple
+  ## instead
+  var
+    sepPos = -1
+  for i in countdown(len(path)-1, 0):
+    if path[i] in {dirsep, altsep}:
+      sepPos = i
+      break
+  if sepPos >= 0:
+    head = copy(path, 0, sepPos-1)
+    tail = copy(path, sepPos+1)
+  else:
+    head = ""
+    tail = path # make a string copy here
+
+proc SplitPath*(path: string): tuple[head, tail: string] {.noSideEffect.} =
+  ## Splits a directory into (head, tail), so that
+  ## ``JoinPath(head, tail) == path``.
+  ##
+  ## Examples: 
+  ## .. code-block:: nimrod
+  ##   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 = copy(path, 0, sepPos-1)
+    result.tail = copy(path, sepPos+1)
+  else:
+    result.head = ""
+    result.tail = path
+
+proc parentDir*(path: string): string {.noSideEffect.} =
+  ## Returns the parent directory of `path`.
+  ##
+  ## This is often the same as the ``head`` result of ``splitPath``.
+  ## If there is no parent, ``path`` is returned.
+  ## Example: ``parentDir("/usr/local/bin") == "/usr/local"``.
+  ## Example: ``parentDir("/usr/local/bin/") == "/usr/local"``.
+  var
+    sepPos = -1
+    q = 1
+  if path[len(path)-1] in {dirsep, altsep}:
+    q = 2
+  for i in countdown(len(path)-q, 0):
+    if path[i] in {dirsep, altsep}:
+      sepPos = i
+      break
+  if sepPos >= 0:
+    result = copy(path, 0, sepPos-1)
+  else:
+    result = path
+
+proc `/../` * (head, tail: string): string {.noSideEffect.} =
+  ## The same as ``parentDir(head) / tail``
+  return parentDir(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.} =
+  ## Splits a filename into (dir, filename, extension).
+  ## `dir` does not end in `DirSep`.
+  ## `extension` includes the leading dot.
+  ##
+  ## Example:
+  ##
+  ## .. code-block:: nimrod
+  ##   var (dir, name, ext) = splitFile("usr/local/nimrodc.html")
+  ##   assert dir == "usr/local"
+  ##   assert name == "nimrodc"
+  ##   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: dotPos = i
+      elif path[i] in {dirsep, altsep}:
+        sepPos = i
+        break
+    result.dir = copy(path, 0, sepPos-1)
+    result.name = copy(path, sepPos+1, dotPos-1)
+    result.ext = copy(path, dotPos)
+
+proc extractDir*(path: string): string {.noSideEffect, deprecated.} =
+  ## Extracts the directory of a given path. This is almost the
+  ## same as the `head` result of `splitPath`, except that
+  ## ``extractDir("/usr/lib/") == "/usr/lib/"``.
+  ## **Deprecated since version 0.8.2**: Use ``splitFile(path).dir`` instead.
+  result = splitFile(path).dir
+
+proc extractFilename*(path: string): string {.noSideEffect.} =
+  ## 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 =
+  ## Returns the full path of `filename`, raises EOS in case of an error.
+  when defined(windows):
+    var unused: cstring
+    result = newString(3072)
+    var L = GetFullPathNameA(filename, 3072'i32, result, unused)
+    if L <= 0'i32 or L >= 3072'i32: OSError()
+    setLen(result, L)
+  else:
+    var res = realpath(filename, nil)
+    if res == nil: OSError()
+    result = $res
+    c_free(res)
+
+proc SplitFilename*(filename: string, name, extension: var string) {.
+  noSideEffect, deprecated.} = 
+  ## Splits a filename into (name, extension), so that
+  ## ``name & extension == filename``.
+  ##
+  ## Example: After ``SplitFilename("usr/local/nimrodc.html", name, ext)``,
+  ## `name` is "usr/local/nimrodc" and `ext` is ".html".
+  ## If the file has no extension, extension is the empty string.
+  ## **Deprecated since version 0.8.2**: Use ``splitFile(filename)`` instead.
+  var extPos = searchExtPos(filename)
+  if extPos >= 0:
+    name = copy(filename, 0, extPos-1)
+    extension = copy(filename, extPos)
+  else:
+    name = filename # make a string copy here
+    extension = ""
+
+proc extractFileExt*(filename: string): string {.noSideEffect, deprecated.} =
+  ## Extracts the file extension of a given `filename`. This is the
+  ## same as the `extension` result of `splitFilename`.
+  ## **Deprecated since version 0.8.2**: Use ``splitFile(filename).ext``
+  ## instead.
+  result = splitFile(filename).ext
+
+proc extractFileTrunk*(filename: string): string {.noSideEffect, deprecated.} =
+  ## Extracts the file name of a given `filename`. This removes any
+  ## directory information and the file extension.
+  ## **Deprecated since version 0.8.2**: Use ``splitFile(path).name`` instead.
+  result = splitFile(filename).name
+  
+proc ChangeFileExt*(filename, ext: string): string {.noSideEffect.} =
+  ## 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 = copy(filename, 0, extPos-1) & normExt(ext)
+
+proc addFileExt*(filename, ext: string): string {.noSideEffect.} =
+  ## 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 AppendFileExt*(filename, ext: string): string {.
+  noSideEffect, deprecated.} =
+  ## **Deprecated since version 0.8.2**: Use `addFileExt` instead.
+  result = addFileExt(filename, ext)
+
+proc cmpPaths*(pathA, pathB: string): int {.noSideEffect.} =
+  ## 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 sameFile*(path1, path2: string): bool =
+  ## Returns True if both pathname arguments refer to the same file or
+  ## directory (as indicated by device number and i-node number).
+  ## Raises an exception if an os.stat() call on either pathname fails.
+  when defined(Windows):
+    var
+      a, b: TWin32FindData
+    var resA = findfirstFileA(path1, a)
+    var resB = findfirstFileA(path2, b)
+    if resA != -1 and resB != -1:
+      result = $a.cFileName == $b.cFileName
+    else:
+      # work around some ``findfirstFileA`` bugs
+      result = cmpPaths(path1, path2) == 0
+    if resA != -1: findclose(resA)
+    if resB != -1: findclose(resB)
+  else:
+    var
+      a, b: TStat
+    if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32:
+      result = cmpPaths(path1, path2) == 0 # be consistent with Windows
+    else:
+      result = a.st_dev == b.st_dev and a.st_ino == b.st_ino
+
+proc sameFileContent*(path1, path2: string): bool =
+  ## Returns True if both pathname arguments refer to files with identical
+  ## binary content.
+  const
+    bufSize = 8192 # 8K buffer
+  var
+    a, b: TFile
+  if not open(a, path1): return false
+  if not open(b, path2):
+    close(a)
+    return false
+  var bufA = alloc(bufsize)
+  var bufB = alloc(bufsize)
+  while True:
+    var readA = readBuffer(a, bufA, bufsize)
+    var readB = readBuffer(b, bufB, bufsize)
+    if readA != readB:
+      result = false
+      break
+    if readA == 0:
+      result = true
+      break
+    result = equalMem(bufA, bufB, readA)
+    if not result: break
+    if readA != bufSize: break # end of file
+  dealloc(bufA)
+  dealloc(bufB)
+  close(a)
+  close(b)
+
+proc copyFile*(dest, source: string) =
+  ## Copies a file from `source` to `dest`. If this fails,
+  ## `EOS` is raised.
+  when defined(Windows):
+    if CopyFileA(source, dest, 0'i32) == 0'i32: OSError()
+  else:
+    # generic version of copyFile which works for any platform:
+    const
+      bufSize = 8192 # 8K buffer
+    var
+      d, s: TFile
+    if not open(s, source): OSError()
+    if not open(d, dest, fmWrite):
+      close(s)
+      OSError()
+    var
+      buf: Pointer = alloc(bufsize)
+      bytesread, byteswritten: int
+    while True:
+      bytesread = readBuffer(s, buf, bufsize)
+      byteswritten = writeBuffer(d, buf, bytesread)
+      if bytesread != bufSize: break
+      if bytesread != bytesWritten: OSError()
+    dealloc(buf)
+    close(s)
+    close(d)
+
+proc moveFile*(dest, source: string) =
+  ## Moves a file from `source` to `dest`. If this fails, `EOS` is raised.
+  if crename(source, dest) != 0'i32: OSError()
+
+proc removeFile*(file: string) =
+  ## Removes the `file`. If this fails, `EOS` is raised.
+  if cremove(file) != 0'i32: OSError()
+
+proc executeShellCommand*(command: string): int {.deprecated.} =
+  ## **Deprecated since version 0.8.2**: Use `execShellCmd` instead.
+  result = csystem(command)
+
+proc execShellCmd*(command: string): int =
+  ## Executes a shell command.
+  ##
+  ## Command has the form 'program args' where args are the command
+  ## line arguments given to program. The proc returns the error code
+  ## of the shell when it has finished. The proc does not return until
+  ## the process has finished. To execute a program without having a
+  ## shell involved, use the `execProcess` proc of the `osproc`
+  ## module.
+  result = csystem(command)
+
+var
+  envComputed: bool = false
+  environment: seq[string] = @[]
+
+when defined(windows):
+  # because we support Windows GUI applications, things get really
+  # messy here...
+  proc strEnd(cstr: CString, c = 0'i32): CString {.
+    importc: "strchr", header: "<string.h>".}
+
+  proc getEnvVarsC() =
+    if not envComputed:
+      var
+        env = getEnvironmentStringsA()
+        e = env
+      if e == nil: return # an error occured
+      while True:
+        var eend = strEnd(e)
+        add(environment, $e)
+        e = cast[CString](cast[TAddress](eend)+1)
+        if eend[1] == '\0': break
+      envComputed = true
+      discard FreeEnvironmentStringsA(env)
+
+else:
+  var
+    gEnv {.importc: "gEnv".}: ptr array [0..10_000, CString]
+
+  proc getEnvVarsC() =
+    # retrieves the variables of char** env of C's main proc
+    if not envComputed:
+      var i = 0
+      while True:
+        if gEnv[i] == nil: break
+        add environment, $gEnv[i]
+        inc(i)
+      envComputed = true
+
+proc findEnvVar(key: string): int =
+  getEnvVarsC()
+  var temp = key & '='
+  for i in 0..high(environment):
+    if startsWith(environment[i], temp): return i
+  return -1
+
+proc getEnv*(key: string): string =
+  ## Returns the value of the environment variable named `key`.
+  ##
+  ## If the variable does not exist, "" is returned. To distinguish
+  ## whether a variable exists or it's value is just "", call
+  ## `existsEnv(key)`.
+  var i = findEnvVar(key)
+  if i >= 0:
+    return copy(environment[i], find(environment[i], '=')+1)
+  else:
+    var env = cgetenv(key)
+    if env == nil: return ""
+    result = $env
+
+proc existsEnv*(key: string): bool =
+  ## Checks whether the environment variable named `key` exists.
+  ## Returns true if it exists, false otherwise.
+  if cgetenv(key) != nil: return true
+  else: return findEnvVar(key) >= 0
+
+proc putEnv*(key, val: string) =
+  ## Sets the value of the environment variable named `key` to `val`.
+  ## If an error occurs, `EInvalidEnvVar` is raised.
+
+  # Note: by storing the string in the environment sequence,
+  # we gurantee that we don't free the memory before the program
+  # ends (this is needed for POSIX compliance). It is also needed so that
+  # the process itself may access its modified environment variables!
+  var indx = findEnvVar(key)
+  if indx >= 0:
+    environment[indx] = key & '=' & val
+  else:
+    add environment, (key & '=' & val)
+    indx = high(environment)
+  when defined(unix):
+    if cputenv(environment[indx]) != 0'i32:
+      OSError()
+  else:
+    if SetEnvironmentVariableA(key, val) == 0'i32:
+      OSError()
+
+iterator iterOverEnvironment*(): tuple[key, value: string] {.deprecated.} =
+  ## Iterate over all environments variables. In the first component of the
+  ## tuple is the name of the current variable stored, in the second its value.
+  ## **Deprecated since version 0.8.2**: Use `envPairs` instead.
+  getEnvVarsC()
+  for i in 0..high(environment):
+    var p = find(environment[i], '=')
+    yield (copy(environment[i], 0, p-1), copy(environment[i], p+1))
+
+iterator envPairs*(): tuple[key, value: string] =
+  ## Iterate over all environments variables. In the first component of the
+  ## tuple is the name of the current variable stored, in the second its value.
+  getEnvVarsC()
+  for i in 0..high(environment):
+    var p = find(environment[i], '=')
+    yield (copy(environment[i], 0, p-1), copy(environment[i], p+1))
+
+iterator walkFiles*(pattern: string): string =
+  ## Iterate over all the files that match the `pattern`.
+  ##
+  ## `pattern` is OS dependant, but at least the "\*.ext"
+  ## notation is supported.
+  when defined(windows):
+    var
+      f: TWin32FindData
+      res: int
+    res = findfirstFileA(pattern, f)
+    if res != -1:
+      while true:
+        if f.cFileName[0] != '.':
+          yield splitFile(pattern).dir / extractFilename($f.cFileName)
+        if findnextFileA(res, f) == 0'i32: break
+      findclose(res)
+  else: # here we use glob
+    var
+      f: TGlob
+      res: int
+    f.gl_offs = 0
+    f.gl_pathc = 0
+    f.gl_pathv = nil
+    res = glob(pattern, 0, nil, addr(f))
+    if res == 0:
+      for i in 0.. f.gl_pathc - 1:
+        assert(f.gl_pathv[i] != nil)
+        yield $f.gl_pathv[i]
+    globfree(addr(f))
+
+type
+  TPathComponent* = enum  ## Enumeration specifying a path component.
+    pcFile,               ## path refers to a file
+    pcLinkToFile,         ## path refers to a symbolic link to a file
+    pcDir,                ## path refers to a directory
+    pcLinkToDir           ## path refers to a symbolic link to a directory
+
+const
+  pcDirectory* {.deprecated.} = pcDir ## deprecated alias 
+  pcLinkToDirectory* {.deprecated.} = pcLinkToDir ## deprecated alias
+
+iterator walkDir*(dir: string): tuple[kind: TPathComponent, path: string] =
+  ## walks over the directory `dir` and yields for each directory or file in
+  ## `dir`. The component type and full path for each item is returned.
+  ## Walking is not recursive.
+  ## Example: This directory structure::
+  ##   dirA / dirB / fileB1.txt
+  ##        / dirC
+  ##        / fileA1.txt
+  ##        / fileA2.txt
+  ##
+  ## and this code:
+  ##
+  ## .. code-block:: Nimrod
+  ##     for kind, path in walkDir("dirA"):
+  ##       echo(path)
+  ##
+  ## produces this output (though not necessarily in this order!)::
+  ##   dirA/dirB
+  ##   dirA/dirC
+  ##   dirA/fileA1.txt
+  ##   dirA/fileA2.txt
+  when defined(windows):
+    var f: TWIN32_Find_Data
+    var h = findfirstFileA(dir / "*", f)
+    if h != -1:
+      while true:
+        var k = pcFile
+        if f.cFilename[0] != '.':
+          if (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32:
+            k = pcDir
+          yield (k, dir / extractFilename($f.cFilename))
+        if findnextFileA(h, f) == 0'i32: break
+      findclose(h)
+  else:
+    var d = openDir(dir)
+    if d != nil:
+      while true:
+        var x = readDir(d)
+        if x == nil: break
+        var y = $x.d_name
+        if y != "." and y != "..":
+          var s: TStat
+          y = dir / y
+          if stat(y, s) < 0'i32: break
+          var k = pcFile
+          if S_ISDIR(s.st_mode): k = pcDir
+          if S_ISLNK(s.st_mode): k = succ(k)
+          yield (k, y)
+      discard closeDir(d)
+
+iterator walkDirRec*(dir: string, filter={pcFile, pcDir}): string =
+  ## walks over the directory `dir` and yields for each file in `dir`. The 
+  ## full path for each file is returned.
+  ## Walking is recursive. `filter` controls the behaviour of the iterator:
+  ##
+  ## ---------------------   ---------------------------------------------
+  ## filter                  meaning
+  ## ---------------------   ---------------------------------------------
+  ## ``pcFile``              yield real files
+  ## ``pcLinkToFile``        yield symbolic links to files
+  ## ``pcDir``               follow real directories
+  ## ``pcLinkToDir``         follow symbolic links to directories
+  ## ---------------------   ---------------------------------------------
+  ## 
+  var stack = @[dir]
+  while stack.len > 0:
+    for k,p in walkDir(stack.pop()):
+      if k in filter:
+        case k
+        of pcFile, pcLinkToFile: yield p
+        of pcDir, pcLinkToDir: stack.add(p)
+
+proc rawRemoveDir(dir: string) = 
+  when defined(windows):
+    if RemoveDirectoryA(dir) == 0'i32: OSError()
+  else:
+    if rmdir(dir) != 0'i32: OSError()
+
+proc removeDir*(dir: string) =
+  ## Removes the directory `dir` including all subdirectories and files
+  ## in `dir` (recursively). If this fails, `EOS` is raised.
+  for kind, path in walkDir(dir): 
+    case kind
+    of pcFile, pcLinkToFile, pcLinkToDir: removeFile(path)
+    of pcDir: removeDir(path)
+  rawRemoveDir(dir)
+
+proc rawCreateDir(dir: string) =
+  when defined(unix):
+    if mkdir(dir, 0o711) != 0'i32 and errno != EEXIST:
+      OSError()
+  else:
+    if CreateDirectoryA(dir, nil) == 0'i32 and GetLastError() != 183'i32:
+      OSError()
+
+proc createDir*(dir: string) =
+  ## Creates the directory `dir`.
+  ##
+  ## The directory may contain several subdirectories that do not exist yet.
+  ## The full path is created. If this fails, `EOS` is raised. It does **not**
+  ## fail if the path already exists because for most usages this does not 
+  ## indicate an error.
+  for i in 1.. dir.len-1:
+    if dir[i] in {dirsep, altsep}: rawCreateDir(copy(dir, 0, i-1))
+  rawCreateDir(dir)
+
+proc parseCmdLine*(c: string): seq[string] =
+  ## Splits a command line into several components; components are separated by
+  ## whitespace unless the whitespace occurs within ``"`` or ``'`` quotes. 
+  ## This proc is only occassionally useful, better use the `parseopt` module.
+  result = @[]
+  var i = 0
+  var a = ""
+  while true:
+    setLen(a, 0)
+    while c[i] >= '\1' and c[i] <= ' ': inc(i) # skip whitespace
+    case c[i]
+    of '\'', '\"':
+      var delim = c[i]
+      inc(i) # skip ' or "
+      while c[i] != '\0' and c[i] != delim:
+        add a, c[i]
+        inc(i)
+      if c[i] != '\0': inc(i)
+    of '\0': break
+    else:
+      while c[i] > ' ':
+        add(a, c[i])
+        inc(i)
+    add(result, a)
+
+type
+  TFilePermission* = enum  ## file access permission; modelled after UNIX
+    fpUserExec,            ## execute access for the file owner
+    fpUserWrite,           ## write access for the file owner
+    fpUserRead,            ## read access for the file owner
+    fpGroupExec,           ## execute access for the group
+    fpGroupWrite,          ## write access for the group
+    fpGroupRead,           ## read access for the group
+    fpOthersExec,          ## execute access for others
+    fpOthersWrite,         ## write access for others
+    fpOthersRead           ## read access for others
+
+proc getFilePermissions*(filename: string): set[TFilePermission] =
+  ## 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.
+  when defined(posix):
+    var a: TStat
+    if stat(filename, a) < 0'i32: OSError()
+    result = {}
+    if (a.st_mode and S_IRUSR) != 0'i32: result.incl(fpUserRead)
+    if (a.st_mode and S_IWUSR) != 0'i32: result.incl(fpUserWrite)
+    if (a.st_mode and S_IXUSR) != 0'i32: result.incl(fpUserExec)
+
+    if (a.st_mode and S_IRGRP) != 0'i32: result.incl(fpGroupRead)
+    if (a.st_mode and S_IWGRP) != 0'i32: result.incl(fpGroupWrite)
+    if (a.st_mode and S_IXGRP) != 0'i32: result.incl(fpGroupExec)
+
+    if (a.st_mode and S_IROTH) != 0'i32: result.incl(fpOthersRead)
+    if (a.st_mode and S_IWOTH) != 0'i32: result.incl(fpOthersWrite)
+    if (a.st_mode and S_IXOTH) != 0'i32: result.incl(fpOthersExec)
+  else:
+    var res = GetFileAttributesA(filename)
+    if res == -1'i32: OSError()
+    if (res and FILE_ATTRIBUTE_READONLY) != 0'i32:
+      result = {fpUserExec, fpUserRead, fpGroupExec, fpGroupRead, 
+                fpOthersExec, fpOthersRead}
+    else:
+      result = {fpUserExec..fpOthersRead}
+  
+proc setFilePermissions*(filename: string, permissions: set[TFilePermission]) =
+  ## 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``.
+  when defined(posix):
+    var p = 0'i32
+    if fpUserRead in permissions: p = p or S_IRUSR
+    if fpUserWrite in permissions: p = p or S_IWUSR
+    if fpUserExec in permissions: p = p or S_IXUSR
+    
+    if fpGroupRead in permissions: p = p or S_IRGRP
+    if fpGroupWrite in permissions: p = p or S_IWGRP
+    if fpGroupExec in permissions: p = p or S_IXGRP
+    
+    if fpOthersRead in permissions: p = p or S_IROTH
+    if fpOthersWrite in permissions: p = p or S_IWOTH
+    if fpOthersExec in permissions: p = p or S_IXOTH
+    
+    if chmod(filename, p) != 0: OSError()
+  else:
+    var res = GetFileAttributesA(filename)
+    if res == -1'i32: OSError()
+    if fpUserWrite in permissions: 
+      res = res and not FILE_ATTRIBUTE_READONLY
+    else:
+      res = res or FILE_ATTRIBUTE_READONLY
+    if SetFileAttributesA(filename, res) != 0'i32: 
+      OSError()
+  
+proc inclFilePermissions*(filename: string, 
+                          permissions: set[TFilePermission]) =
+  ## a convenience procedure for: 
+  ##
+  ## .. code-block:: nimrod
+  ##   setFilePermissions(filename, getFilePermissions(filename)+permissions)
+  setFilePermissions(filename, getFilePermissions(filename)+permissions)
+
+proc exclFilePermissions*(filename: string, 
+                          permissions: set[TFilePermission]) =
+  ## a convenience procedure for: 
+  ##
+  ## .. code-block:: nimrod
+  ##   setFilePermissions(filename, getFilePermissions(filename)-permissions)
+  setFilePermissions(filename, getFilePermissions(filename)-permissions)
+
+proc getHomeDir*(): string =
+  ## Returns the home directory of the current user.
+  when defined(windows): return getEnv("USERPROFILE") & "\\"
+  else: return getEnv("HOME") & "/"
+
+proc getConfigDir*(): string =
+  ## Returns the config directory of the current user for applications.
+  when defined(windows): return getEnv("APPDATA") & "\\"
+  else: return getEnv("HOME") & "/.config/"
+
+when defined(windows):
+  # Since we support GUI applications with Nimrod, we sometimes generate
+  # a WinMain entry proc. But a WinMain proc has no access to the parsed
+  # command line arguments. The way to get them differs. Thus we parse them
+  # ourselves. This has the additional benefit that the program's behaviour
+  # is always the same -- independent of the used C compiler.
+  var
+    ownArgv: seq[string]
+
+  proc paramStr(i: int): string =
+    if isNil(ownArgv): ownArgv = parseCmdLine($getCommandLineA())
+    return ownArgv[i]
+
+  proc paramCount(): int =
+    if isNil(ownArgv): ownArgv = parseCmdLine($getCommandLineA())
+    result = ownArgv.len-1
+
+else:
+  var
+    cmdCount {.importc: "cmdCount".}: cint
+    cmdLine {.importc: "cmdLine".}: cstringArray
+
+  proc paramStr(i: int): string =
+    if i < cmdCount and i >= 0: return $cmdLine[i]
+    raise newException(EInvalidIndex, "invalid index")
+
+  proc paramCount(): int = return cmdCount-1
+
+when 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)
+    if len > 256:
+      result = newString(len+1)
+      len = readlink(procPath, result, len)
+    setlen(result, len)
+
+when defined(macosx):
+  # a really hacky solution: since we like to include 2 headers we have to
+  # define two procs which in reality are the same
+  proc getExecPath1(c: cstring, size: var int32) {.
+    importc: "_NSGetExecutablePath", header: "<sys/param.h>".}
+  proc getExecPath2(c: cstring, size: var int32): bool {.
+    importc: "_NSGetExecutablePath", header: "<mach-o/dyld.h>".}
+
+proc getApplicationFilename*(): string =
+  ## Returns the filename of the application's executable.
+
+  # Linux: /proc/<pid>/exe
+  # Solaris:
+  # /proc/<pid>/object/a.out (filename only)
+  # /proc/<pid>/path/a.out (complete pathname)
+  # *BSD (and maybe Darwin too):
+  # /proc/<pid>/file
+  when defined(windows):
+    result = newString(256)
+    var len = getModuleFileNameA(0, result, 256)
+    setlen(result, int(len))
+  elif defined(linux) or defined(aix):
+    result = getApplAux("/proc/self/exe")
+  elif defined(solaris):
+    result = getApplAux("/proc/" & $getpid() & "/path/a.out")
+  elif defined(bsd):
+    result = getApplAux("/proc/" & $getpid() & "/file")
+  elif defined(macosx):
+    var size: int32
+    getExecPath1(nil, size)
+    result = newString(int(size))
+    if getExecPath2(result, size):
+      result = "" # error!
+  else:
+    # little heuristic that may work on other POSIX-like systems:
+    result = getEnv("_")
+    if len(result) == 0:
+      result = ParamStr(0) # POSIX guaranties that this contains the executable
+                           # as it has been executed by the calling process
+      if len(result) > 0 and result[0] != DirSep: # not an absolute path?
+        # iterate over any path in the $PATH environment variable
+        for p in split(getEnv("PATH"), {PathSep}):
+          var x = joinPath(p, result)
+          if ExistsFile(x): return x
+
+proc getApplicationDir*(): string =
+  ## Returns the directory of the application's executable.
+  result = splitFile(getApplicationFilename()).dir
+
+proc sleep*(milsecs: int) =
+  ## sleeps `milsecs` milliseconds.
+  when defined(windows):
+    winlean.sleep(int32(milsecs))
+  else:
+    var a, b: Ttimespec
+    a.tv_sec = TTime(milsecs div 1000)
+    a.tv_nsec = (milsecs mod 1000) * 1000
+    discard posix.nanosleep(a, b)
+
+{.pop.}