diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2018-09-07 01:53:09 +0200 |
---|---|---|
committer | Araq <rumpf_a@web.de> | 2018-09-07 19:21:16 +0200 |
commit | 86556ebfdbbd4b8e9edc9d3085ea21d6c0bae2e2 (patch) | |
tree | 9f8b4b752ed728ceff82dceef2f5605cb2a846a0 /compiler/pathutils.nim | |
parent | b5730ec01f02e542eb06161430aa5a7c2ede775f (diff) | |
download | Nim-86556ebfdbbd4b8e9edc9d3085ea21d6c0bae2e2.tar.gz |
compiler refactoring; use typesafe path handing; docgen: render symbols between modules
Diffstat (limited to 'compiler/pathutils.nim')
-rw-r--r-- | compiler/pathutils.nim | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/compiler/pathutils.nim b/compiler/pathutils.nim new file mode 100644 index 000000000..f84d964bb --- /dev/null +++ b/compiler/pathutils.nim @@ -0,0 +1,254 @@ +# +# +# The Nim Compiler +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Path handling utilities for Nim. Strictly typed code in order +## to avoid the never ending time sink in getting path handling right. +## Might be a candidate for the stdlib later. + +import os, strutils + +type + AbsoluteFile* = distinct string + AbsoluteDir* = distinct string + RelativeFile* = distinct string + RelativeDir* = distinct string + +proc isEmpty*(x: AbsoluteFile): bool {.inline.} = x.string.len == 0 +proc isEmpty*(x: AbsoluteDir): bool {.inline.} = x.string.len == 0 +proc isEmpty*(x: RelativeFile): bool {.inline.} = x.string.len == 0 +proc isEmpty*(x: RelativeDir): bool {.inline.} = x.string.len == 0 + +proc copyFile*(source, dest: AbsoluteFile) = + os.copyFile(source.string, dest.string) + +proc removeFile*(x: AbsoluteFile) {.borrow.} + +proc splitFile*(x: AbsoluteFile): tuple[dir: AbsoluteDir, name, ext: string] = + let (a, b, c) = splitFile(x.string) + result = (dir: AbsoluteDir(a), name: b, ext: c) + +proc extractFilename*(x: AbsoluteFile): string {.borrow.} + +proc fileExists*(x: AbsoluteFile): bool {.borrow.} +proc dirExists*(x: AbsoluteDir): bool {.borrow.} + +proc quoteShell*(x: AbsoluteFile): string {.borrow.} +proc quoteShell*(x: AbsoluteDir): string {.borrow.} + +proc cmpPaths*(x, y: AbsoluteDir): int {.borrow.} + +proc createDir*(x: AbsoluteDir) {.borrow.} + +type + PathIter = object + i, prev: int + notFirst: bool + +proc hasNext(it: PathIter; x: string): bool = + it.i < x.len + +proc next(it: var PathIter; x: string): (int, int) = + it.prev = it.i + if not it.notFirst and x[it.i] in {DirSep, AltSep}: + # absolute path: + inc it.i + else: + while it.i < x.len and x[it.i] notin {DirSep, AltSep}: inc it.i + if it.i > it.prev: + result = (it.prev, it.i-1) + elif hasNext(it, x): + result = next(it, x) + + # skip all separators: + while it.i < x.len and x[it.i] in {DirSep, AltSep}: inc it.i + it.notFirst = true + +iterator dirs(x: string): (int, int) = + var it: PathIter + while hasNext(it, x): yield next(it, x) + +when false: + iterator dirs(x: string): (int, int) = + var i = 0 + var first = true + while i < x.len: + let prev = i + if first and x[i] in {DirSep, AltSep}: + # absolute path: + inc i + else: + while i < x.len and x[i] notin {DirSep, AltSep}: inc i + if i > prev: + yield (prev, i-1) + first = false + # skip all separators: + while i < x.len and x[i] in {DirSep, AltSep}: inc i + +proc isDot(x: string; bounds: (int, int)): bool = + bounds[1] == bounds[0] and x[bounds[0]] == '.' + +proc isDotDot(x: string; bounds: (int, int)): bool = + bounds[1] == bounds[0] + 1 and x[bounds[0]] == '.' and x[bounds[0]+1] == '.' + +proc isSlash(x: string; bounds: (int, int)): bool = + bounds[1] == bounds[0] and x[bounds[0]] in {DirSep, AltSep} + +proc canon(x: string; result: var string; state: var int) = + # state: 0th bit set if isAbsolute path. Other bits count + # the number of path components. + for b in dirs(x): + if (state shr 1 == 0) and isSlash(x, b): + result.add DirSep + state = state or 1 + elif result.len > (state and 1) and isDotDot(x, b): + var d = result.len + # f/.. + while d > (state and 1) and result[d-1] != DirSep: + dec d + setLen(result, d) + elif isDot(x, b): + discard "discard the dot" + else: + if result.len > (state and 1): result.add DirSep + result.add substr(x, b[0], b[1]) + inc state, 2 + +proc canon(x: string): string = + # - Turn multiple slashes into single slashes. + # - Resolve '/foo/../bar' to '/bar'. + # - Remove './' from the path. + result = newStringOfCap(x.len) + var state = 0 + canon(x, result, state) + +when FileSystemCaseSensitive: + template `!=?`(a, b: char): bool = toLowerAscii(a) != toLowerAscii(b) +else: + template `!=?`(a, b: char): bool = a != b + +proc relativeTo(full, base: string; sep = DirSep): string = + if full.len == 0: return "" + var f, b: PathIter + var ff = (0, -1) + var bb = (0, -1) # (int, int) + result = newStringOfCap(full.len) + # skip the common prefix: + while f.hasNext(full) and b.hasNext(base): + ff = next(f, full) + 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 full[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 -- full 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 'full': + 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 full[i + ff[0]] + if not f.hasNext(full): break + ff = f.next(full) + +when true: + proc eqImpl(x, y: string): bool = + when FileSystemCaseSensitive: + result = toLowerAscii(canon x) == toLowerAscii(canon y) + else: + result = canon(x) == canon(y) + + proc `==`*(x, y: AbsoluteFile): bool = eqImpl(x.string, y.string) + proc `==`*(x, y: AbsoluteDir): bool = eqImpl(x.string, y.string) + proc `==`*(x, y: RelativeFile): bool = eqImpl(x.string, y.string) + proc `==`*(x, y: RelativeDir): bool = eqImpl(x.string, y.string) + + proc `/`*(base: AbsoluteDir; f: RelativeFile): AbsoluteFile = + assert isAbsolute(base.string) + assert(not isAbsolute(f.string)) + result = AbsoluteFile newStringOfCap(base.string.len + f.string.len) + var state = 0 + canon(base.string, result.string, state) + canon(f.string, result.string, state) + + proc `/`*(base: AbsoluteDir; f: RelativeDir): AbsoluteDir = + assert isAbsolute(base.string) + assert(not isAbsolute(f.string)) + result = AbsoluteDir newStringOfCap(base.string.len + f.string.len) + var state = 0 + canon(base.string, result.string, state) + canon(f.string, result.string, state) + + proc relativeTo*(fullPath: AbsoluteFile, baseFilename: AbsoluteDir; + sep = DirSep): RelativeFile = + RelativeFile(relativeTo(fullPath.string, baseFilename.string, sep)) + + proc toAbsolute*(file: string; base: AbsoluteDir): AbsoluteFile = + if isAbsolute(file): result = AbsoluteFile(file) + else: result = base / RelativeFile file + + proc changeFileExt*(x: AbsoluteFile; ext: string): AbsoluteFile {.borrow.} + proc changeFileExt*(x: RelativeFile; ext: string): RelativeFile {.borrow.} + + proc addFileExt*(x: AbsoluteFile; ext: string): AbsoluteFile {.borrow.} + proc addFileExt*(x: RelativeFile; ext: string): RelativeFile {.borrow.} + + proc writeFile*(x: AbsoluteFile; content: string) {.borrow.} + +when isMainModule and defined(posix): + doAssert canon"/foo/../bar" == "/bar" + doAssert canon"foo/../bar" == "bar" + + doAssert canon"/f/../bar///" == "/bar" + doAssert canon"f/..////bar" == "bar" + + doAssert canon"../bar" == "../bar" + doAssert canon"/../bar" == "/../bar" + + doAssert canon("foo/../../bar/") == "../bar" + doAssert canon("./bla/blob/") == "bla/blob" + doAssert canon(".hiddenFile") == ".hiddenFile" + doAssert canon("./bla/../../blob/./zoo.nim") == "../blob/zoo.nim" + + doAssert canon("C:/file/to/this/long") == "C:/file/to/this/long" + doAssert canon("") == "" + doAssert canon("foobar") == "foobar" + doAssert canon("f/////////") == "f" + + doAssert relativeTo("/foo/bar//baz.nim", "/foo") == "bar/baz.nim" + + doAssert relativeTo("/Users/me/bar/z.nim", "/Users/other/bad") == "../../me/bar/z.nim" + + doAssert relativeTo("/Users/me/bar/z.nim", "/Users/other") == "../me/bar/z.nim" + doAssert relativeTo("/Users///me/bar//z.nim", "//Users/") == "me/bar/z.nim" + doAssert relativeTo("/Users/me/bar/z.nim", "/Users/me") == "bar/z.nim" + doAssert relativeTo("", "/users/moo") == "" + doAssert relativeTo("foo", "") == "foo" + + echo string(AbsoluteDir"/Users/me///" / RelativeFile"z.nim") |