12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031 |
- include system/inclrtl
- import std/private/since
- import std/[strutils, pathnorm]
- import std/oserrors
- import oscommon
- export ReadDirEffect, WriteDirEffect
- when defined(nimPreviewSlimSystem):
- import std/[syncio, assertions, widestrs]
- ## .. importdoc:: osappdirs.nim, osdirs.nim, osseps.nim, os.nim
- const weirdTarget = defined(nimscript) or defined(js)
- when weirdTarget:
- discard
- elif defined(windows):
- import std/winlean
- elif defined(posix):
- import std/posix, system/ansi_c
- else:
- {.error: "OS module not ported to your operating system!".}
- when weirdTarget:
- {.pragma: noWeirdTarget, error: "this proc is not available on the NimScript/js target".}
- else:
- {.pragma: noWeirdTarget.}
- when defined(nimscript):
- # for procs already defined in scriptconfig.nim
- template noNimJs(body): untyped = discard
- elif defined(js):
- {.pragma: noNimJs, error: "this proc is not available on the js target".}
- else:
- {.pragma: noNimJs.}
- proc normalizePathAux(path: var string){.inline, raises: [], noSideEffect.}
- import std/private/osseps
- export osseps
- proc absolutePathInternal(path: string): string {.gcsafe.}
- proc normalizePathEnd*(path: var string, trailingSep = false) =
- ## Ensures ``path`` has exactly 0 or 1 trailing `DirSep`, depending on
- ## ``trailingSep``, and taking care of edge cases: it preservers whether
- ## a path is absolute or relative, and makes sure trailing sep is `DirSep`,
- ## not `AltSep`. Trailing `/.` are compressed, see examples.
- if path.len == 0: return
- var i = path.len
- while i >= 1:
- if path[i-1] in {DirSep, AltSep}: dec(i)
- elif path[i-1] == '.' and i >= 2 and path[i-2] in {DirSep, AltSep}: dec(i)
- else: break
- if trailingSep:
- # foo// => foo
- path.setLen(i)
- # foo => foo/
- path.add DirSep
- elif i > 0:
- # foo// => foo
- path.setLen(i)
- else:
- # // => / (empty case was already taken care of)
- path = $DirSep
- proc normalizePathEnd*(path: string, trailingSep = false): string =
- ## outplace overload
- runnableExamples:
- when defined(posix):
- assert normalizePathEnd("/lib//.//", trailingSep = true) == "/lib/"
- assert normalizePathEnd("lib/./.", trailingSep = false) == "lib"
- assert normalizePathEnd(".//./.", trailingSep = false) == "."
- assert normalizePathEnd("", trailingSep = true) == "" # not / !
- assert normalizePathEnd("/", trailingSep = false) == "/" # not "" !
- result = path
- result.normalizePathEnd(trailingSep)
- template endsWith(a: string, b: set[char]): bool =
- a.len > 0 and a[^1] in b
- proc joinPathImpl(result: var string, state: var int, tail: string) =
- let trailingSep = tail.endsWith({DirSep, AltSep}) or tail.len == 0 and result.endsWith({DirSep, AltSep})
- normalizePathEnd(result, trailingSep=false)
- addNormalizePath(tail, result, state, DirSep)
- normalizePathEnd(result, trailingSep=trailingSep)
- proc joinPath*(head, tail: string): string {.
- noSideEffect, rtl, extern: "nos$1".} =
- ## 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:
- ## * `joinPath(parts: varargs[string]) proc`_
- ## * `/ proc`_
- ## * `splitPath proc`_
- ## * `uri.combine proc <uri.html#combine,Uri,Uri>`_
- ## * `uri./ proc <uri.html#/,Uri,string>`_
- runnableExamples:
- when defined(posix):
- assert joinPath("usr", "lib") == "usr/lib"
- assert joinPath("usr", "lib/") == "usr/lib/"
- assert joinPath("usr", "") == "usr"
- assert joinPath("usr/", "") == "usr/"
- assert joinPath("", "") == ""
- assert joinPath("", "lib") == "lib"
- assert joinPath("", "/lib") == "/lib"
- assert joinPath("usr/", "/lib") == "usr/lib"
- assert joinPath("usr/lib", "../bin") == "usr/bin"
- result = newStringOfCap(head.len + tail.len)
- var state = 0
- joinPathImpl(result, state, head)
- joinPathImpl(result, state, tail)
- when false:
- if len(head) == 0:
- result = tail
- elif head[len(head)-1] in {DirSep, AltSep}:
- if tail.len > 0 and tail[0] in {DirSep, AltSep}:
- result = head & substr(tail, 1)
- else:
- result = head & tail
- else:
- if tail.len > 0 and tail[0] in {DirSep, AltSep}:
- result = head & tail
- else:
- result = head & DirSep & tail
- proc joinPath*(parts: varargs[string]): string {.noSideEffect,
- rtl, extern: "nos$1OpenArray".} =
- ## The same as `joinPath(head, tail) proc`_,
- ## 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.
- ##
- ## See also:
- ## * `joinPath(head, tail) proc`_
- ## * `/ proc`_
- ## * `/../ proc`_
- ## * `splitPath proc`_
- runnableExamples:
- when defined(posix):
- assert joinPath("a") == "a"
- assert joinPath("a", "b", "c") == "a/b/c"
- assert joinPath("usr/lib", "../../var", "log") == "var/log"
- var estimatedLen = 0
- for p in parts: estimatedLen += p.len
- result = newStringOfCap(estimatedLen)
- var state = 0
- for i in 0..high(parts):
- joinPathImpl(result, state, parts[i])
- proc `/`*(head, tail: string): string {.noSideEffect, inline.} =
- ## The same as `joinPath(head, tail) proc`_.
- ##
- ## See also:
- ## * `/../ proc`_
- ## * `joinPath(head, tail) proc`_
- ## * `joinPath(parts: varargs[string]) proc`_
- ## * `splitPath proc`_
- ## * `uri.combine proc <uri.html#combine,Uri,Uri>`_
- ## * `uri./ proc <uri.html#/,Uri,string>`_
- runnableExamples:
- when defined(posix):
- assert "usr" / "" == "usr"
- assert "" / "lib" == "lib"
- assert "" / "/lib" == "/lib"
- assert "usr/" / "/lib/" == "usr/lib/"
- assert "usr" / "lib" / "../bin" == "usr/bin"
- result = joinPath(head, tail)
- when doslikeFileSystem:
- import std/private/ntpath
- proc splitPath*(path: string): tuple[head, tail: string] {.
- noSideEffect, rtl, extern: "nos$1".} =
- ## Splits a directory into `(head, tail)` tuple, so that
- ## ``head / tail == path`` (except for edge cases like "/usr").
- ##
- ## See also:
- ## * `joinPath(head, tail) proc`_
- ## * `joinPath(parts: varargs[string]) proc`_
- ## * `/ proc`_
- ## * `/../ proc`_
- ## * `relativePath proc`_
- runnableExamples:
- assert splitPath("usr/local/bin") == ("usr/local", "bin")
- assert splitPath("usr/local/bin/") == ("usr/local/bin", "")
- assert splitPath("/bin/") == ("/bin", "")
- when (NimMajor, NimMinor) <= (1, 0):
- assert splitPath("/bin") == ("", "bin")
- else:
- assert splitPath("/bin") == ("/", "bin")
- assert splitPath("bin") == ("", "bin")
- assert splitPath("") == ("", "")
- when doslikeFileSystem:
- let (drive, splitpath) = splitDrive(path)
- let stop = drive.len
- else:
- const stop = 0
- var sepPos = -1
- for i in countdown(len(path)-1, stop):
- if path[i] in {DirSep, AltSep}:
- sepPos = i
- break
- if sepPos >= 0:
- result.head = substr(path, 0,
- when (NimMajor, NimMinor) <= (1, 0):
- sepPos-1
- else:
- if likely(sepPos >= 1): sepPos-1 else: 0
- )
- result.tail = substr(path, sepPos+1)
- else:
- when doslikeFileSystem:
- result.head = drive
- result.tail = splitpath
- else:
- result.head = ""
- result.tail = path
- proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1", raises: [].} =
- ## Checks whether a given `path` is absolute.
- ##
- ## On Windows, network paths are considered absolute too.
- runnableExamples:
- assert not "".isAbsolute
- assert not ".".isAbsolute
- when defined(posix):
- assert "/".isAbsolute
- assert not "a/".isAbsolute
- assert "/a/".isAbsolute
- if len(path) == 0: return false
- when doslikeFileSystem:
- var len = len(path)
- result = (path[0] in {'/', '\\'}) or
- (len > 1 and path[0] in {'a'..'z', 'A'..'Z'} and path[1] == ':')
- elif defined(macos):
- # according to https://perldoc.perl.org/File/Spec/Mac.html `:a` is a relative path
- result = path[0] != ':'
- elif defined(RISCOS):
- result = path[0] == '$'
- elif defined(posix):
- result = path[0] == '/'
- elif defined(nodejs):
- {.emit: [result," = require(\"path\").isAbsolute(",path.cstring,");"].}
- else:
- raiseAssert "unreachable" # if ever hits here, adapt as needed
- when FileSystemCaseSensitive:
- template `!=?`(a, b: char): bool = a != b
- else:
- template `!=?`(a, b: char): bool = toLowerAscii(a) != toLowerAscii(b)
- when doslikeFileSystem:
- proc isAbsFromCurrentDrive(path: string): bool {.noSideEffect, raises: [].} =
- ## An absolute path from the root of the current drive (e.g. "\foo")
- path.len > 0 and
- (path[0] == AltSep or
- (path[0] == DirSep and
- (path.len == 1 or path[1] notin {DirSep, AltSep, ':'})))
- proc sameRoot(path1, path2: string): bool {.noSideEffect, raises: [].} =
- ## Return true if path1 and path2 have a same root.
- ##
- ## Detail of Windows path formats:
- ## https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats
- assert(isAbsolute(path1))
- assert(isAbsolute(path2))
- if isAbsFromCurrentDrive(path1) and isAbsFromCurrentDrive(path2):
- result = true
- elif cmpIgnoreCase(splitDrive(path1).drive, splitDrive(path2).drive) == 0:
- result = true
- else:
- result = false
- proc relativePath*(path, base: string, sep = DirSep): string {.
- rtl, extern: "nos$1".} =
- ## 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`_
- runnableExamples:
- assert relativePath("/Users/me/bar/z.nim", "/Users/other/bad", '/') == "../../me/bar/z.nim"
- assert relativePath("/Users/me/bar/z.nim", "/Users/other", '/') == "../me/bar/z.nim"
- when not doslikeFileSystem: # On Windows, UNC-paths start with `//`
- assert relativePath("/Users///me/bar//z.nim", "//Users/", '/') == "me/bar/z.nim"
- assert relativePath("/Users/me/bar/z.nim", "/Users/me", '/') == "bar/z.nim"
- assert relativePath("", "/users/moo", '/') == ""
- assert relativePath("foo", ".", '/') == "foo"
- assert relativePath("foo", "foo", '/') == "."
- if path.len == 0: return ""
- var base = if base == ".": "" else: base
- var path = path
- path.normalizePathAux
- base.normalizePathAux
- let a1 = isAbsolute(path)
- let a2 = isAbsolute(base)
- if a1 and not a2:
- base = absolutePathInternal(base)
- elif a2 and not a1:
- path = absolutePathInternal(path)
- when doslikeFileSystem:
- if isAbsolute(path) and isAbsolute(base):
- if not sameRoot(path, base):
- return path
- var f = default PathIter
- var b = default PathIter
- var ff = (0, -1)
- var bb = (0, -1) # (int, int)
- result = newStringOfCap(path.len)
- # skip the common prefix:
- while f.hasNext(path) and b.hasNext(base):
- ff = next(f, path)
- bb = next(b, base)
- let diff = ff[1] - ff[0]
- if diff != bb[1] - bb[0]: break
- var same = true
- for i in 0..diff:
- if path[i + ff[0]] !=? base[i + bb[0]]:
- same = false
- break
- if not same: break
- ff = (0, -1)
- bb = (0, -1)
- # for i in 0..diff:
- # result.add base[i + bb[0]]
- # /foo/bar/xxx/ -- base
- # /foo/bar/baz -- path path
- # ../baz
- # every directory that is in 'base', needs to add '..'
- while true:
- if bb[1] >= bb[0]:
- if result.len > 0 and result[^1] != sep:
- result.add sep
- result.add ".."
- if not b.hasNext(base): break
- bb = b.next(base)
- # add the rest of 'path':
- while true:
- if ff[1] >= ff[0]:
- if result.len > 0 and result[^1] != sep:
- result.add sep
- for i in 0..ff[1] - ff[0]:
- result.add path[i + ff[0]]
- if not f.hasNext(path): break
- ff = f.next(path)
- when not defined(nimOldRelativePathBehavior):
- if result.len == 0: result.add "."
- proc isRelativeTo*(path: string, base: string): bool {.since: (1, 1).} =
- ## Returns true if `path` is relative to `base`.
- runnableExamples:
- doAssert isRelativeTo("./foo//bar", "foo")
- doAssert isRelativeTo("foo/bar", ".")
- doAssert isRelativeTo("/foo/bar.nim", "/foo/bar.nim")
- doAssert not isRelativeTo("foo/bar.nims", "foo/bar.nim")
- let path = path.normalizePath
- let base = base.normalizePath
- let ret = relativePath(path, base)
- result = path.len > 0 and not ret.startsWith ".."
- 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 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`_
- runnableExamples:
- assert parentDir("") == ""
- when defined(posix):
- assert parentDir("/usr/local/bin") == "/usr/local"
- assert parentDir("foo/bar//") == "foo"
- assert parentDir("//foo//bar//.") == "/foo"
- assert parentDir("./foo") == "."
- assert parentDir("/./foo//./") == "/"
- assert parentDir("a//./") == "."
- assert parentDir("a/b/c/..") == "a"
- result = pathnorm.normalizePath(path)
- when doslikeFileSystem:
- let (drive, splitpath) = splitDrive(result)
- result = splitpath
- var sepPos = parentDirPos(result)
- if sepPos >= 0:
- result = substr(result, 0, sepPos)
- normalizePathEnd(result)
- elif result == ".." or result == "." or result.len == 0 or result[^1] in {DirSep, AltSep}:
- # `.` => `..` and .. => `../..`(etc) would be a sensible alternative
- # `/` => `/` (as done with splitFile) would be a sensible alternative
- result = ""
- else:
- result = "."
- when doslikeFileSystem:
- if result.len == 0:
- discard
- elif drive.len > 0 and result.len == 1 and result[0] in {DirSep, AltSep}:
- result = drive
- else:
- result = drive & result
- proc tailDir*(path: string): string {.
- noSideEffect, rtl, extern: "nos$1".} =
- ## Returns the tail part of `path`.
- ##
- ## See also:
- ## * `relativePath proc`_
- ## * `splitPath proc`_
- ## * `parentDir proc`_
- runnableExamples:
- assert tailDir("/bin") == "bin"
- assert tailDir("bin") == ""
- assert tailDir("bin/") == ""
- assert tailDir("/usr/local/bin") == "usr/local/bin"
- assert tailDir("//usr//local//bin//") == "usr//local//bin//"
- assert tailDir("./usr/local/bin") == "usr/local/bin"
- assert tailDir("usr/local/bin") == "local/bin"
- var i = 0
- when doslikeFileSystem:
- let (drive, splitpath) = path.splitDrive
- if drive != "":
- return splitpath.strip(chars = {DirSep, AltSep}, trailing = false)
- while i < len(path):
- if path[i] in {DirSep, AltSep}:
- while i < len(path) and path[i] in {DirSep, AltSep}: inc i
- return substr(path, i)
- inc i
- result = ""
- proc isRootDir*(path: string): bool {.
- noSideEffect, rtl, extern: "nos$1".} =
- ## Checks whether a given `path` is a root directory.
- runnableExamples:
- assert isRootDir("")
- assert isRootDir(".")
- assert isRootDir("/")
- assert isRootDir("a")
- assert not isRootDir("/a")
- assert not isRootDir("a/b/c")
- when doslikeFileSystem:
- if splitDrive(path).path == "":
- return true
- 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 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`_
- ##
- runnableExamples:
- let g = "a/b/c"
- for p in g.parentDirs:
- echo p
- # a/b/c
- # a/b
- # a
- for p in g.parentDirs(fromRoot=true):
- echo p
- # a/
- # a/b/
- # a/b/c
- for p in g.parentDirs(inclusive=false):
- echo p
- # a/b
- # a
- if not fromRoot:
- var current = path
- if inclusive: yield path
- while true:
- if current.isRootDir: break
- current = current.parentDir
- yield current
- else:
- when doslikeFileSystem:
- let start = path.splitDrive.drive.len
- else:
- const start = 0
- for i in countup(start, 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.
- ##
- ## See also:
- ## * `/ proc`_
- ## * `parentDir proc`_
- runnableExamples:
- when defined(posix):
- assert "a/b/c" /../ "d/e" == "a/b/d/e"
- assert "a" /../ "d/e" == "a/d/e"
- when doslikeFileSystem:
- let (drive, head) = splitDrive(head)
- let sepPos = parentDirPos(head)
- if sepPos >= 0:
- result = substr(head, 0, sepPos-1) / tail
- else:
- result = head / tail
- when doslikeFileSystem:
- result = drive / result
- proc normExt(ext: string): string =
- if ext == "" or ext[0] == ExtSep: result = ext # no copy needed here
- else: result = ExtSep & ext
- proc searchExtPos*(path: string): int =
- ## Returns index of the `'.'` char in `path` if it signifies the beginning
- ## of the file extension. Returns -1 otherwise.
- ##
- ## See also:
- ## * `splitFile proc`_
- ## * `extractFilename proc`_
- ## * `lastPathPart proc`_
- ## * `changeFileExt proc`_
- ## * `addFileExt proc`_
- runnableExamples:
- assert searchExtPos("a/b/c") == -1
- assert searchExtPos("c.nim") == 1
- assert searchExtPos("a/b/c.nim") == 5
- assert searchExtPos("a.b.c.nim") == 5
- assert searchExtPos(".nim") == -1
- assert searchExtPos("..nim") == -1
- assert searchExtPos("a..nim") == 2
- # Unless there is any char that is not `ExtSep` before last `ExtSep` in the file name,
- # it is not a file extension.
- const DirSeps = when doslikeFileSystem: {DirSep, AltSep, ':'} else: {DirSep, AltSep}
- result = -1
- var i = path.high
- while i >= 1:
- if path[i] == ExtSep:
- break
- elif path[i] in DirSeps:
- return -1 # do not skip over path
- dec i
- for j in countdown(i - 1, 0):
- if path[j] in DirSeps:
- return -1
- elif path[j] != ExtSep:
- result = i
- break
- proc splitFile*(path: string): tuple[dir, name, ext: string] {.
- noSideEffect, rtl, extern: "nos$1".} =
- ## 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:
- ## * `searchExtPos proc`_
- ## * `extractFilename proc`_
- ## * `lastPathPart proc`_
- ## * `changeFileExt proc`_
- ## * `addFileExt proc`_
- runnableExamples:
- var (dir, name, ext) = splitFile("usr/local/nimc.html")
- assert dir == "usr/local"
- assert name == "nimc"
- assert ext == ".html"
- (dir, name, ext) = splitFile("/usr/local/os")
- assert dir == "/usr/local"
- assert name == "os"
- assert ext == ""
- (dir, name, ext) = splitFile("/usr/local/")
- assert dir == "/usr/local"
- assert name == ""
- assert ext == ""
- (dir, name, ext) = splitFile("/tmp.txt")
- assert dir == "/"
- assert name == "tmp"
- assert ext == ".txt"
- var namePos = 0
- var dotPos = 0
- when doslikeFileSystem:
- let (drive, _) = splitDrive(path)
- let stop = len(drive)
- result.dir = drive
- else:
- const stop = 0
- for i in countdown(len(path) - 1, stop):
- if path[i] in {DirSep, AltSep} or i == 0:
- if path[i] in {DirSep, AltSep}:
- result.dir = substr(path, 0, if likely(i >= 1): i - 1 else: 0)
- namePos = i + 1
- if dotPos > i:
- result.name = substr(path, namePos, dotPos - 1)
- result.ext = substr(path, dotPos)
- else:
- result.name = substr(path, namePos)
- break
- elif path[i] == ExtSep and i > 0 and i < len(path) - 1 and
- path[i - 1] notin {DirSep, AltSep} and
- path[i + 1] != ExtSep and dotPos == 0:
- dotPos = i
- 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) proc`_.
- ##
- ## See also:
- ## * `searchExtPos proc`_
- ## * `splitFile proc`_
- ## * `lastPathPart proc`_
- ## * `changeFileExt proc`_
- ## * `addFileExt proc`_
- runnableExamples:
- assert extractFilename("foo/bar/") == ""
- assert extractFilename("foo/bar") == "bar"
- assert extractFilename("foo/bar.baz") == "bar.baz"
- if path.len == 0 or path[path.len-1] in {DirSep, AltSep}:
- result = ""
- else:
- result = splitPath(path).tail
- proc lastPathPart*(path: string): string {.
- noSideEffect, rtl, extern: "nos$1".} =
- ## Like `extractFilename proc`_, but ignores
- ## trailing dir separator; aka: `baseName`:idx: in some other languages.
- ##
- ## See also:
- ## * `searchExtPos proc`_
- ## * `splitFile proc`_
- ## * `extractFilename proc`_
- ## * `changeFileExt proc`_
- ## * `addFileExt proc`_
- runnableExamples:
- assert lastPathPart("foo/bar/") == "bar"
- assert lastPathPart("foo/bar") == "bar"
- let path = path.normalizePathEnd(trailingSep = false)
- result = extractFilename(path)
- proc changeFileExt*(filename, ext: string): string {.
- noSideEffect, rtl, extern: "nos$1".} =
- ## Changes the file extension to `ext`.
- ##
- ## If the `filename` has no extension, `ext` will be added.
- ## If `ext` == "" then any extension is removed.
- ##
- ## `Ext` should be given without the leading `'.'`, because some
- ## filesystems may use a different character. (Although I know
- ## of none such beast.)
- ##
- ## See also:
- ## * `searchExtPos proc`_
- ## * `splitFile proc`_
- ## * `extractFilename proc`_
- ## * `lastPathPart proc`_
- ## * `addFileExt proc`_
- runnableExamples:
- assert changeFileExt("foo.bar", "baz") == "foo.baz"
- assert changeFileExt("foo.bar", "") == "foo"
- assert changeFileExt("foo", "baz") == "foo.baz"
- 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.)
- ##
- ## See also:
- ## * `searchExtPos proc`_
- ## * `splitFile proc`_
- ## * `extractFilename proc`_
- ## * `lastPathPart proc`_
- ## * `changeFileExt proc`_
- runnableExamples:
- assert addFileExt("foo.bar", "baz") == "foo.bar"
- assert addFileExt("foo.bar", "") == "foo.bar"
- assert addFileExt("foo", "baz") == "foo.baz"
- 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` if pathA == pathB
- ## | `< 0` if pathA < pathB
- ## | `> 0` if pathA > pathB
- runnableExamples:
- when defined(macosx):
- assert cmpPaths("foo", "Foo") == 0
- elif defined(posix):
- assert cmpPaths("foo", "Foo") > 0
- let a = normalizePath(pathA)
- let b = normalizePath(pathB)
- if FileSystemCaseSensitive:
- result = cmp(a, b)
- else:
- when defined(nimscript):
- result = cmpic(a, b)
- elif defined(nimdoc): discard
- else:
- result = cmpIgnoreCase(a, b)
- proc unixToNativePath*(path: string, drive=""): string {.
- noSideEffect, rtl, extern: "nos$1".} =
- ## Converts an UNIX-like path to a native one.
- ##
- ## On an UNIX system this does nothing. Else it converts
- ## `'/'`, `'.'`, `'..'` to the appropriate things.
- ##
- ## On systems with a concept of "drives", `drive` is used to determine
- ## which drive label to use during absolute path conversion.
- ## `drive` defaults to the drive of the current working directory, and is
- ## ignored on systems that do not have a concept of "drives".
- when defined(unix):
- result = path
- else:
- if path.len == 0: return ""
- var start: int
- if path[0] == '/':
- # an absolute path
- when doslikeFileSystem:
- if drive != "":
- result = drive & ":" & DirSep
- else:
- result = $DirSep
- elif defined(macos):
- result = "" # must not start with ':'
- else:
- result = $DirSep
- start = 1
- elif path[0] == '.' and (path.len == 1 or path[1] == '/'):
- # current directory
- result = $CurDir
- start = when doslikeFileSystem: 1 else: 2
- else:
- result = ""
- start = 0
- var i = start
- while i < len(path): # ../../../ --> ::::
- if i+2 < path.len and path[i] == '.' and path[i+1] == '.' and path[i+2] == '/':
- # parent directory
- when defined(macos):
- if result[high(result)] == ':':
- add result, ':'
- else:
- add result, ParDir
- else:
- add result, ParDir & DirSep
- inc(i, 3)
- elif path[i] == '/':
- add result, DirSep
- inc(i)
- else:
- add result, path[i]
- inc(i)
- when not defined(nimscript):
- proc getCurrentDir*(): string {.rtl, extern: "nos$1", 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`_
- ## * `getConfigDir proc`_
- ## * `getTempDir proc`_
- ## * `setCurrentDir proc`_
- ## * `currentSourcePath template <system.html#currentSourcePath.t>`_
- ## * `getProjectPath proc <macros.html#getProjectPath>`_
- when defined(nodejs):
- var ret: cstring
- {.emit: "`ret` = process.cwd();".}
- return $ret
- elif defined(js):
- raiseAssert "use -d:nodejs to have `getCurrentDir` defined"
- elif defined(windows):
- var bufsize = MAX_PATH.int32
- var res = newWideCString(bufsize)
- while true:
- var L = getCurrentDirectoryW(bufsize, res)
- if L == 0'i32:
- raiseOSError(osLastError())
- elif L > bufsize:
- res = newWideCString(L)
- bufsize = L
- else:
- result = res$L
- break
- else:
- var bufsize = 1024 # should be enough
- result = newString(bufsize)
- while true:
- if getcwd(result.cstring, bufsize) != nil:
- setLen(result, c_strlen(result.cstring))
- break
- else:
- let err = osLastError()
- if err.int32 == ERANGE:
- bufsize = bufsize shl 1
- doAssert(bufsize >= 0)
- result = newString(bufsize)
- else:
- raiseOSError(osLastError())
- proc absolutePath*(path: string, root = getCurrentDir()): string =
- ## 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:
- ## * `normalizedPath proc`_
- ## * `normalizePath proc`_
- runnableExamples:
- assert absolutePath("a") == getCurrentDir() / "a"
- if isAbsolute(path): path
- else:
- if not root.isAbsolute:
- raise newException(ValueError, "The specified root is not absolute: " & root)
- joinPath(root, path)
- proc absolutePathInternal(path: string): string =
- absolutePath(path, getCurrentDir())
- proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [].} =
- ## Normalize a path.
- ##
- ## Consecutive directory separators are collapsed, including an initial double slash.
- ##
- ## On relative paths, double dot (`..`) sequences are collapsed if possible.
- ## On absolute paths they are always collapsed.
- ##
- ## .. warning:: URL-encoded and Unicode attempts at directory traversal are not detected.
- ## Triple dot is not handled.
- ##
- ## See also:
- ## * `absolutePath proc`_
- ## * `normalizedPath proc`_ for outplace version
- ## * `normalizeExe proc`_
- runnableExamples:
- when defined(posix):
- var a = "a///b//..//c///d"
- a.normalizePath()
- assert a == "a/c/d"
- path = pathnorm.normalizePath(path)
- when false:
- let isAbs = isAbsolute(path)
- var stack: seq[string] = @[]
- for p in split(path, {DirSep}):
- case p
- of "", ".":
- continue
- of "..":
- if stack.len == 0:
- if isAbs:
- discard # collapse all double dots on absoluta paths
- else:
- stack.add(p)
- elif stack[^1] == "..":
- stack.add(p)
- else:
- discard stack.pop()
- else:
- stack.add(p)
- if isAbs:
- path = DirSep & join(stack, $DirSep)
- elif stack.len > 0:
- path = join(stack, $DirSep)
- else:
- path = "."
- proc normalizePathAux(path: var string) = normalizePath(path)
- proc normalizedPath*(path: string): string {.rtl, extern: "nos$1", tags: [].} =
- ## Returns a normalized path for the current OS.
- ##
- ## See also:
- ## * `absolutePath proc`_
- ## * `normalizePath proc`_ for the in-place version
- runnableExamples:
- when defined(posix):
- assert normalizedPath("a///b//..//c///d") == "a/c/d"
- result = pathnorm.normalizePath(path)
- proc normalizeExe*(file: var string) {.since: (1, 3, 5).} =
- ## on posix, prepends `./` if `file` doesn't contain `/` and is not `"", ".", ".."`.
- runnableExamples:
- import std/sugar
- when defined(posix):
- doAssert "foo".dup(normalizeExe) == "./foo"
- doAssert "foo/../bar".dup(normalizeExe) == "foo/../bar"
- doAssert "".dup(normalizeExe) == ""
- when defined(posix):
- if file.len > 0 and DirSep notin file and file != "." and file != "..":
- file = "./" & file
- proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1",
- tags: [ReadDirEffect], noWeirdTarget.} =
- ## Returns true if both pathname arguments refer to the same physical
- ## file or directory.
- ##
- ## Raises `OSError` if any of the files does not
- ## exist or information about it can not be obtained.
- ##
- ## This proc will return true if given two alternative hard-linked or
- ## sym-linked paths to the same file or directory.
- ##
- ## See also:
- ## * `sameFileContent proc`_
- when defined(windows):
- var success = true
- var f1 = openHandle(path1)
- var f2 = openHandle(path2)
- var lastErr: OSErrorCode
- if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE:
- var fi1, fi2: BY_HANDLE_FILE_INFORMATION
- if getFileInformationByHandle(f1, addr(fi1)) != 0 and
- getFileInformationByHandle(f2, addr(fi2)) != 0:
- result = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber and
- fi1.nFileIndexHigh == fi2.nFileIndexHigh and
- fi1.nFileIndexLow == fi2.nFileIndexLow
- else:
- lastErr = osLastError()
- success = false
- else:
- lastErr = osLastError()
- success = false
- discard closeHandle(f1)
- discard closeHandle(f2)
- if not success: raiseOSError(lastErr, $(path1, path2))
- else:
- var a, b: Stat
- if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32:
- raiseOSError(osLastError(), $(path1, path2))
- else:
- result = a.st_dev == b.st_dev and a.st_ino == b.st_ino
|