summary refs log tree commit diff stats
path: root/lib/posix
Commit message (Expand)AuthorAgeFilesLines
* Made TSocketHandle distinct and fixed problems it caused for SSL.Dominik Picheta2013-10-231-22/+26
* Added a TSocketHandle type to winlean.nim and posix.nimClay Sweetser2013-10-151-0/+3
* some fixes for HaikuAraq2013-09-011-45/+49
* Removes executable bit for text files.Grzegorz Adam Hankiewicz2013-03-161-0/+0
* Added fsmonitor module.Dominik Picheta2012-09-021-0/+70
* Many fixes for asynchronous sockets. Asyncio should now work well with buffer...Dominik Picheta2012-07-221-1/+1
* changed integer promotion rules; added math.fmodAraq2012-06-281-1/+1
* produce errors on proc types with implicit empty param lists.Zahary Karadjov2012-04-201-2/+2
* implemented incompleteStruct pragma; embedded debugger works with posix moduleAraq2012-01-051-3/+12
* year 2012 for most copyright headersAraq2012-01-021-1/+1
* attempt to fix tunidecode test; GC cares for seq->openArray conversionsAraq2011-11-211-3/+5
* new osproc implementation may work with mac os xAraq2011-11-181-1/+7
* cgen: no type canon for integral types; osproc use posix_spawn instead of for...Araq2011-11-181-6/+7
* The sockets module supports non-blocking sockets now. Many other fixes in soc...dom962011-04-301-3/+10
* docgen: module dependencies are now listedAraq2011-01-161-3/+3
* fixed pango/pangoutils new wrappersAndreas Rumpf2010-02-261-0/+0
* continued work on html/xmlparserrumpf_a@web.de2010-02-141-0/+0
* bugfix: fd_set mustn't be prefixed with structAndreas Rumpf2010-01-141-1/+1
* sockets module completeAndreas Rumpf2010-01-131-97/+186
* version 0.8.2rumpf_a@web.de2009-10-211-0/+2
* added tools and web dirsAndreas Rumpf2009-09-151-0/+0
* overload resolution for proc varsAndreas Rumpf2009-06-241-1839/+1840
* version0.7.10Andreas Rumpf2009-06-084-2090/+33
* some bugfixesAndreas Rumpf2009-05-141-12/+14
* version 0.7.6Andreas Rumpf2009-04-223-0/+2064
* version 0.7.6Andreas Rumpf2009-04-221-198/+795
* version 0.7.0Andreas Rumpf2008-11-161-135/+152
* too many changes to listAndreas Rumpf2008-08-231-40/+40
* first releaseRumpf2008-06-231-0/+0
* Initial importAndreas Rumpf2008-06-221-0/+1818
le */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
## This module implements path handling.
##
## **See also:**
## * `files module <files.html>`_ for file access

import std/private/osseps
export osseps

import std/envvars
import std/private/osappdirs

import std/[pathnorm, hashes, sugar, strutils]

from std/private/ospaths2 import  joinPath, splitPath,
                                  ReadDirEffect, WriteDirEffect,
                                  isAbsolute, relativePath,
                                  normalizePathEnd, isRelativeTo, parentDir,
                                  tailDir, isRootDir, parentDirs, `/../`,
                                  extractFilename, lastPathPart,
                                  changeFileExt, addFileExt, cmpPaths, splitFile,
                                  unixToNativePath, absolutePath, normalizeExe,
                                  normalizePath
export ReadDirEffect, WriteDirEffect

type
  Path* = distinct string

func hash*(x: Path): Hash =
  let x = x.string.dup(normalizePath)
  if FileSystemCaseSensitive:
    result = x.hash
  else:
    result = x.toLowerAscii.hash

template `$`*(x: Path): string =
  string(x)

func `==`*(x, y: Path): bool {.inline.} =
  ## Compares two paths.
  ##
  ## On a case-sensitive filesystem this is done
  ## case-sensitively otherwise case-insensitively.
  result = cmpPaths(x.string, y.string) == 0

template endsWith(a: string, b: set[char]): bool =
  a.len > 0 and a[^1] in b

func add(x: var string, tail: string) =
  var state = 0
  let trailingSep = tail.endsWith({DirSep, AltSep}) or tail.len == 0 and x.endsWith({DirSep, AltSep})
  normalizePathEnd(x, trailingSep=false)
  addNormalizePath(tail, x, state, DirSep)
  normalizePathEnd(x, trailingSep=trailingSep)

func add*(x: var Path, y: Path) {.borrow.}

func `/`*(head, tail: Path): Path {.inline.} =
  ## Joins two directory names to one.
  ##
  ## returns normalized path concatenation of `head` and `tail`, preserving
  ## whether or not `tail` has a trailing slash (or, if tail if empty, whether
  ## head has one).
  ##
  ## See also:
  ## * `splitPath proc`_
  ## * `uri.combine proc <uri.html#combine,Uri,Uri>`_
  ## * `uri./ proc <uri.html#/,Uri,string>`_
  Path(joinPath(head.string, tail.string))

func splitPath*(path: Path): tuple[head, tail: Path] {.inline.} =
  ## Splits a directory into `(head, tail)` tuple, so that
  ## ``head / tail == path`` (except for edge cases like "/usr").
  ##
  ## See also:
  ## * `add proc`_
  ## * `/ proc`_
  ## * `/../ proc`_
  ## * `relativePath proc`_
  let res = splitPath(path.string)
  result = (Path(res.head), Path(res.tail))

func splitFile*(path: Path): tuple[dir, name: Path, ext: string] {.inline.} =
  ## Splits a filename into `(dir, name, extension)` tuple.
  ##
  ## `dir` does not end in DirSep unless it's `/`.
  ## `extension` includes the leading dot.
  ##
  ## 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.
  ##
  ## See also:
  ## * `extractFilename proc`_
  ## * `lastPathPart proc`_
  ## * `changeFileExt proc`_
  ## * `addFileExt proc`_
  let res = splitFile(path.string)
  result = (Path(res.dir), Path(res.name), res.ext)

func isAbsolute*(path: Path): bool {.inline, raises: [].} =
  ## Checks whether a given `path` is absolute.
  ##
  ## On Windows, network paths are considered absolute too.
  result = isAbsolute(path.string)

proc relativePath*(path, base: Path, sep = DirSep): Path {.inline.} =
  ## Converts `path` to a path relative to `base`.
  ##
  ## The `sep` (default: DirSep) is used for the path normalizations,
  ## this can be useful to ensure the relative path only contains `'/'`
  ## so that it can be used for URL constructions.
  ##
  ## On Windows, if a root of `path` and a root of `base` are different,
  ## returns `path` as is because it is impossible to make a relative path.
  ## That means an absolute path can be returned.
  ##
  ## See also:
  ## * `splitPath proc`_
  ## * `parentDir proc`_
  ## * `tailDir proc`_
  result = Path(relativePath(path.string, base.string, sep))

proc isRelativeTo*(path: Path, base: Path): bool {.inline.} =
  ## Returns true if `path` is relative to `base`.
  result = isRelativeTo(path.string, base.string)


func parentDir*(path: Path): Path {.inline.} =
  ## Returns the parent directory of `path`.
  ##
  ## This is similar to ``splitPath(path).head`` when ``path`` doesn't end
  ## in a dir separator, but also takes care of path normalizations.
  ## The remainder can be obtained with `lastPathPart(path) proc`_.
  ##
  ## See also:
  ## * `relativePath proc`_
  ## * `splitPath proc`_
  ## * `tailDir proc`_
  ## * `parentDirs iterator`_
  result = Path(parentDir(path.string))

func tailDir*(path: Path): Path {.inline.} =
  ## Returns the tail part of `path`.
  ##
  ## See also:
  ## * `relativePath proc`_
  ## * `splitPath proc`_
  ## * `parentDir proc`_
  result = Path(tailDir(path.string))

func isRootDir*(path: Path): bool {.inline.} =
  ## Checks whether a given `path` is a root directory.
  result = isRootDir(path.string)

iterator parentDirs*(path: Path, fromRoot=false, inclusive=true): Path =
  ## Walks over all parent directories of a given `path`.
  ##
  ## If `fromRoot` is true (default: false), the traversal will start from
  ## the file system root directory.
  ## If `inclusive` is true (default), the original argument will be included
  ## in the traversal.
  ##
  ## Relative paths won't be expanded by this iterator. Instead, it will traverse
  ## only the directories appearing in the relative path.
  ##
  ## See also:
  ## * `parentDir proc`_
  ##
  for p in parentDirs(path.string, fromRoot, inclusive):
    yield Path(p)

func `/../`*(head, tail: Path): Path {.inline.} =
  ## The same as ``parentDir(head) / tail``, unless there is no parent
  ## directory. Then ``head / tail`` is performed instead.
  ##
  ## See also:
  ## * `/ proc`_
  ## * `parentDir proc`_
  Path(`/../`(head.string, tail.string))

func extractFilename*(path: Path): Path {.inline.} =
  ## Extracts the filename of a given `path`.
  ##
  ## This is the same as ``name & ext`` from `splitFile(path) proc`_.
  ##
  ## See also:
  ## * `splitFile proc`_
  ## * `lastPathPart proc`_
  ## * `changeFileExt proc`_
  ## * `addFileExt proc`_
  result = Path(extractFilename(path.string))

func lastPathPart*(path: Path): Path {.inline.} =
  ## Like `extractFilename proc`_, but ignores
  ## trailing dir separator; aka: `baseName`:idx: in some other languages.
  ##
  ## See also:
  ## * `splitFile proc`_
  ## * `extractFilename proc`_
  ## * `changeFileExt proc`_
  ## * `addFileExt proc`_
  result = Path(lastPathPart(path.string))

func changeFileExt*(filename: Path, ext: string): Path {.inline.} =
  ## 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.)
  ##
  ## See also:
  ## * `splitFile proc`_
  ## * `extractFilename proc`_
  ## * `lastPathPart proc`_
  ## * `addFileExt proc`_
  result = Path(changeFileExt(filename.string, ext))

func addFileExt*(filename: Path, ext: string): Path {.inline.} =
  ## 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.)
  ##
  ## See also:
  ## * `splitFile proc`_
  ## * `extractFilename proc`_
  ## * `lastPathPart proc`_
  ## * `changeFileExt proc`_
  result = Path(addFileExt(filename.string, ext))

func unixToNativePath*(path: Path, drive=Path("")): Path {.inline.} =
  ## 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".
  result = Path(unixToNativePath(path.string, drive.string))

proc getCurrentDir*(): Path {.inline, tags: [].} =
  ## Returns the `current working directory`:idx: i.e. where the built
  ## binary is run.
  ##
  ## So the path returned by this proc is determined at run time.
  ##
  ## See also:
  ## * `getHomeDir proc <appdirs.html#getHomeDir>`_
  ## * `getConfigDir proc <appdirs.html#getConfigDir>`_
  ## * `getTempDir proc <appdirs.html#getTempDir>`_
  ## * `setCurrentDir proc <dirs.html#setCurrentDir>`_
  ## * `currentSourcePath template <system.html#currentSourcePath.t>`_
  ## * `getProjectPath proc <macros.html#getProjectPath>`_
  result = Path(ospaths2.getCurrentDir())

proc normalizeExe*(file: var Path) {.borrow.}

proc normalizePath*(path: var Path) {.borrow.}

proc normalizePathEnd*(path: var Path, trailingSep = false) {.borrow.}

proc absolutePath*(path: Path, root = getCurrentDir()): Path =
  ## Returns the absolute path of `path`, rooted at `root` (which must be absolute;
  ## default: current directory).
  ## If `path` is absolute, return it, ignoring `root`.
  ##
  ## See also:
  ## * `normalizePath proc`_
  result = Path(absolutePath(path.string, root.string))

proc expandTildeImpl(path: string): string {.
  tags: [ReadEnvEffect, ReadIOEffect].} =
  if len(path) == 0 or path[0] != '~':
    result = path
  elif len(path) == 1:
    result = getHomeDir()
  elif (path[1] in {DirSep, AltSep}):
    result = joinPath(getHomeDir(), path.substr(2))
  else:
    # TODO: handle `~bob` and `~bob/` which means home of bob
    result = path

proc expandTilde*(path: Path): Path {.inline,
  tags: [ReadEnvEffect, ReadIOEffect].} =
  ## Expands ``~`` or a path starting with ``~/`` to a full path, replacing
  ## ``~`` with `getHomeDir() <appdirs.html#getHomeDir>`_ (otherwise returns ``path`` unmodified).
  ##
  ## Windows: this is still supported despite the Windows platform not having this
  ## convention; also, both ``~/`` and ``~\`` are handled.
  runnableExamples:
    import std/appdirs
    assert expandTilde(Path("~") / Path("appname.cfg")) == getHomeDir() / Path("appname.cfg")
    assert expandTilde(Path("~/foo/bar")) == getHomeDir() / Path("foo/bar")
    assert expandTilde(Path("/foo/bar")) == Path("/foo/bar")
  result = Path(expandTildeImpl(path.string))