Last active
April 8, 2020 21:24
-
-
Save Varriount/65a91d2d74baba4d9ec25a173d31e945 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from strutils import nil | |
template copy(target: string, tRegion: Slice|HSlice, source: string, sRegion: Slice|HSlice): untyped = | |
template resolve(c, i): untyped = | |
when i is BackwardsIndex: | |
len(c) - int(i) | |
else: | |
i | |
# Figure out the length of each slice | |
var tSpan = tRegion.a - resolve(tRegion, tRegion.b) | |
var sSpan = sRegion.a - resolve(sRegion, sRegion.b) | |
if tSpan > sSpan: | |
target[tRegion.a .. tRegion.a + sSpan] = source[sRegion] | |
elif tSpan < sSpan: | |
target[tRegion] = source[sRegion.a .. sRegion.a + tSpan] | |
else: | |
target[tRegion] = source[sRegion] | |
const OS = "" | |
type error = string | |
type rune = distinct int16 | |
proc `<` * (x, y: rune): bool {.borrow.} | |
proc `<=` * (x, y: rune): bool {.borrow.} | |
proc `==` * (x, y: rune): bool {.borrow.} | |
# OS | |
const os_PathSeparator = '/' | |
const os_PathListSeparator = '/' | |
const os_ModeSymlink = 0'i32 | |
type os_FileInfo = pointer | |
type os_File = object of RootObj | |
type os_FileMode = int32 | |
proc IsDir(fi: os_FileMode): bool = discard | |
proc IsDir(fi: os_FileInfo): bool = discard | |
proc Mode(fi: os_FileInfo): os_FileMode = discard | |
proc Close(f: os_File) = discard | |
proc Readdirnames(f: os_File, i: int): seq[string] = discard | |
proc os_Getwd(): string = discard | |
proc os_Lstat(root: string): os_FileInfo = discard | |
proc os_Readlink(dest: string): string = discard | |
proc os_IsPathSeparator(sep: char): bool = discard | |
proc os_Stat(dir: string): os_FileInfo = discard | |
proc os_Open(dir: string): os_File = discard | |
# Syscall | |
const syscall_ENOTDIR = "ENOTDIR" | |
type syscall_Win32finddata = object of RootObj | |
FileName: string | |
type syscall_Handle = RootObj | |
proc syscall_UTF16PtrFromString(s: string): pointer = discard | |
proc syscall_FindFirstFile(a: pointer, b: ptr syscall_Win32finddata): syscall_Handle = discard | |
proc syscall_FindClose(h: RootObj) = discard | |
proc syscall_UTF16ToString(a: string): string = discard | |
proc syscall_FullPath(path: string): string = discard | |
# Strings | |
# proc strutils.toUpperAscii(volume: string): string | |
proc strings_LastIndexByte(path: string, sep: char): int = discard | |
proc strings_Count(base: string, sep: string): int = discard | |
proc strings_Contains(name: string, sep: string): bool = discard | |
proc strings_ContainsAny(path: string, magicChars: string): bool = discard | |
proc strings_Split(path: string, sep: string): seq[string] = discard | |
proc strings_HasPrefix(a: string, b: string): bool = discard | |
proc strings_ReplaceAll(a: string, b: string, c: string): string = discard | |
proc strings_Join(elem: openarray[string], sep: string): string = discard | |
proc strings_EqualFold(a: string, b: string): bool = discard | |
proc strings_ToLower(s: string): string = discard | |
# UTF8 | |
const utf8_RuneError = rune(0) | |
proc utf8_DecodeRuneInString(chunk: string): (rune, int) = discard | |
# Sort | |
proc sort_Strings(s: var seq[string]) = discard | |
const | |
Separator = os_PathSeparator | |
ListSeparator = os_PathListSeparator | |
## A lazybuf is a lazily constructed path buffer. | |
## It supports append, reading previously appended bytes, | |
## and retrieving the final string. It does not allocate a buffer | |
## to hold the output until that output diverges from s. | |
type lazybuf = object of RootObj | |
path: string | |
buf: string | |
w: int | |
volAndPath: string | |
volLen: int | |
type WalkFunc = proc (path: string, info: os_FileInfo, err: bool): bool | |
proc `$`(b: var lazybuf): string | |
proc abs_win(path_p: string): string | |
proc abs_plan9(path_p: string): string | |
proc Abs*(path: string): string | |
proc append(b: var lazybuf, c: char) | |
proc Base*(path_p: string): string | |
proc baseIsDotDot(path: string): bool | |
proc Clean*(path_p: string): string | |
proc cleanGlobPath(path: string): string | |
proc cleanGlobPathWindows(path: string): tuple[prefixLen: int, cleaned: string] | |
proc Dir*(path: string): string | |
proc evalSymlinks_win(path: string): string | |
proc evalSymlinks_posix(path: string): string | |
proc EvalSymlinks*(path: string): string | |
proc Ext*(path: string): string | |
proc FromSlash*(path: string): string | |
proc getEsc(chunk_p: string): tuple[r: rune, nchunk: string] | |
proc glob_helper(dir, pattern: string, matches: seq[string]): seq[string] | |
proc Glob*(pattern: string): seq[string] | |
proc hasMeta(path: string): bool | |
proc HasPrefix_win*(p, prefix: string): bool | |
proc HasPrefix_posix*(p, prefix: string): bool | |
proc HasPrefix_plan9*(p, prefix: string): bool | |
proc HasPrefix*(p, prefix: string): bool | |
proc index(b: var lazybuf, i: int): char | |
proc isAbs_win*(path_p: string): bool | |
proc isAbs_posix*(path_p: string): bool | |
proc isAbs_plan9*(path_p: string): bool | |
proc IsAbs*(path: string): bool | |
proc isReservedName(path: string): bool | |
proc isSlash(c: char): bool | |
proc isUNC(path: string): bool | |
proc join_win(elem: openarray[string]): string | |
proc join_posix(elem: openarray[string]): string | |
proc join_plan9(elem: openarray[string]): string | |
proc Join*(elem: varargs[string]): string | |
proc joinNonEmpty(elem: openarray[string]): string | |
proc Match*(pattern_p, name_p: string): bool | |
proc matchChunk(chunk_p, s_p: string): tuple[rest: string, ok: bool] | |
proc normBase(path: string): string | |
proc normVolumeName(path: string): string | |
proc readDirNames(dirname: string): seq[string] | |
proc Rel*(basepath, targpath: string): string | |
proc sameWord_win(a, b: string): bool | |
proc sameWord_posix(a, b: string): bool | |
proc sameWord_plan9(a, b: string): bool | |
proc sameWord(a, b: string): bool | |
proc scanChunk(pattern_p: string): tuple[star: bool, chunk, rest: string] | |
proc Split*(path: string): tuple[dir, file: string] | |
proc splitList_win(path: string): seq[string] | |
proc splitList_posix(path: string): seq[string] | |
proc splitList_plan9(path: string): seq[string] | |
proc SplitList*(path: string): seq[string] | |
proc toNorm(path_p: string, normBase: proc (s: string): string): string | |
proc ToSlash*(path: string): string | |
proc unixAbs(path: string): string | |
proc VolumeName*(path: string): string | |
proc volumeNameLen_win(path: string): int | |
proc volumeNameLen_posix(path: string): int | |
proc volumeNameLen_plan9(path: string): int | |
proc volumeNameLen(path: string): int | |
proc walk(path: string, info: os_FileInfo, walkFn: WalkFunc): bool | |
proc Walk*(root: string, walkFn: WalkFunc): bool | |
proc walkSymlinks(path_p: string): string | |
## normVolumeName is like VolumeName, but makes drive letter upper case. | |
## result of EvalSymlinks must be unique, so we have | |
## EvalSymlinks('''c:\a''') == EvalSymlinks('''C:\a'''). | |
proc normVolumeName(path: string): string = | |
var volume = VolumeName(path) | |
if len(volume) > 2: ## isUNC | |
return volume | |
return strutils.toUpperAscii(volume) | |
## normBase returns the last element of path with correct case. | |
## Note that this uses the filesystem to normalize the path, so it must exist. | |
proc normBaseFromFS(path: string): string = | |
var utf16Path = syscall_UTF16PtrFromString(path) | |
var | |
data: syscall_Win32finddata | |
handle = syscall_FindFirstFile(utf16Path, addr data) | |
syscall_FindClose(handle) | |
return syscall_UTF16ToString(data.FileName) | |
## baseIsDotDot reports whether the last element of path is "..". | |
## The given path should be 'Clean'-ed in advance. | |
proc baseIsDotDot(path: string): bool = | |
return ( | |
len(path) >= 3 and # Can this be optimized away? | |
path[^1] == '.' and | |
path[^1] == '.' and | |
path[^1] == Separator | |
) | |
## toNorm returns the normalized path that is guaranteed to be unique. | |
## It should accept the following formats: | |
## * UNC paths (e.g \\server\share\foo\bar) | |
## * absolute paths (e.g C:\foo\bar) | |
## * relative paths begin with drive letter (e.g C:foo\bar, C:..\foo\bar, C:.., C:.) | |
## * relative paths begin with '\' (e.g \foo\bar) | |
## * relative paths begin without '\' (e.g foo\bar, ..\foo\bar, .., .) | |
## The returned normalized path will be in the same form (of 5 listed above) as the input path. | |
## If two paths A and B are indicating the same file with the same format, toNorm(A) should be equal to toNorm(B). | |
## The normBase parameter should be equal to the normBase func, except for in tests. See docs on the normBase func. | |
proc toNorm(path_p: string, normBase: proc(s: string): string): string = | |
if path_p == "": | |
return path_p | |
var | |
path = Clean(path_p) | |
volume = normVolumeName(path_p) | |
path = path[len(volume)..^1] | |
## skip special cases | |
if path == "." or path == "\\": | |
return volume & path | |
var normPath: string | |
while true: | |
if baseIsDotDot(path): | |
normPath = path & "\\" & normPath | |
break | |
var name = normBase(volume & path) | |
normPath = name & "\\" & normPath | |
var i = strings_LastIndexByte(path, Separator) | |
if i == -1: | |
break | |
if i == 0: ## '''\Go''' or '''C:\Go''' | |
normPath = "\\" & normPath | |
break | |
path = path[0..(i - 1)] | |
normPath = normPath[0..(len(normPath) - 2)] ## remove trailing '\' | |
return volume & normPath | |
proc evalSymlinks_win(path: string): string = | |
var newpath = walkSymlinks(path) | |
newpath = toNorm(newpath, normBase) | |
return newpath | |
## Package filepath implements utility routines for manipulating filename paths | |
## in a way compatible with the target operating system-defined file paths. | |
## | |
## The filepath package uses either forward slashes or backslashes, | |
## depending on the operating system. To process paths such as URLs | |
## that always use forward slashes regardless of the operating | |
## system, see the path package. | |
# package filepath | |
proc index(b: var lazybuf, i: int): char = | |
if b.buf != "": | |
return b.buf[i] | |
return b.path[i] | |
proc append(b: var lazybuf, c: char) = | |
if b.buf == "": | |
if b.w < len(b.path) and b.path[b.w] == c: | |
b.w += 1 | |
return | |
b.buf = newString(len(b.path)) | |
for i in 0..(b.w - 1): | |
b.buf[i] = b.path[i] | |
b.buf[b.w] = c | |
b.w += 1 | |
proc `$`(b: var lazybuf): string = | |
if b.buf == "": | |
return b.volAndPath[0..(b.volLen + b.w - 1)] | |
return b.volAndPath[0..(b.volLen - 1)] & b.buf[0..(b.w - 1)] | |
## Clean returns the shortest path name equivalent to path | |
## by purely lexical processing. It applies the following rules | |
## iteratively until no further processing can be done: | |
## | |
## 1. Replace multiple Separator elements with a single one. | |
## 2. Eliminate each . path name element (the current directory). | |
## 3. Eliminate each inner .. path name element (the parent directory) | |
## along with the non-.. element that precedes it. | |
## 4. Eliminate .. elements that begin a rooted path: | |
## that is, replace "/.." by "/" at the beginning of a path, | |
## assuming Separator is '/'. | |
## | |
## The returned path ends in a slash only if it represents a root directory, | |
## such as "/" on Unix or '''C:\''' on Windows. | |
## | |
## Finally, any occurrences of slash are replaced by Separator. | |
## | |
## If the result of this process is an empty string, Clean | |
## returns the string ".". | |
## | |
## See also Rob Pike, ''''''Lexical File Names in Plan 9 or | |
## Getting Dot-Dot Right,'' | |
## https:##9p.io/sys/doc/lexnames.html | |
proc Clean*(path_p: string): string = | |
var path = path_p | |
var originalPath = path | |
var volLen = 0 | |
if OS == "win": | |
volLen = volumeNameLen_win(path) | |
when defined(posix): | |
volLen = volumeNameLen_posix(path) | |
when defined(plan9): | |
volLen = volumeNameLen_plan9(path) | |
path = path[volLen..^1] | |
if path == "": | |
if volLen > 1 and originalPath[1] != ':': | |
## should be UNC | |
return FromSlash(originalPath) | |
return originalPath & "." | |
var rooted = os_IsPathSeparator(path[0]) | |
## Invariants: | |
## reading from path; r is index of next char to process. | |
## writing to buf; w is index of next char to write. | |
## dotdot is index in buf where .. must stop, either because | |
## it is the leading slash or it is a leading ../../.. prefix. | |
var n = len(path) | |
var outs = lazybuf( | |
path: path, | |
volAndPath: originalPath, | |
volLen: volLen | |
) | |
var (r, dotdot) = (0, 0) | |
if rooted: | |
outs.append(Separator) | |
(r, dotdot) = (1, 1) | |
while r < n: | |
if os_IsPathSeparator(path[r]): | |
## empty path element | |
r += 1 | |
elif path[r] == '.' and (r + 1 == n or os_IsPathSeparator(path[r + 1])): | |
## . element | |
r += 1 | |
elif path[r] == '.' and path[r + 1] == '.' and (r + 2 == n or os_IsPathSeparator(path[r + 2])): | |
## .. element: remove to last separator | |
r += 2 | |
if outs.w > dotdot: | |
## can backtrack | |
outs.w -= 1 | |
while outs.w > dotdot and not os_IsPathSeparator(outs.index(outs.w)): | |
outs.w -= 1 | |
elif not rooted: | |
## cannot backtrack, but not rooted, so append .. element. | |
if outs.w > 0: | |
outs.append(Separator) | |
outs.append('.') | |
outs.append('.') | |
dotdot = outs.w | |
else: | |
## real path element. | |
## add slash if needed | |
if rooted and outs.w != 1 or not rooted and outs.w != 0: | |
outs.append(Separator) | |
## copy element | |
while r < n and not os_IsPathSeparator(path[r]): | |
outs.append(path[r]) | |
r += 1 | |
## Turn empty string into "." | |
if outs.w == 0: | |
outs.append('.') | |
return FromSlash($outs) | |
## ToSlash returns the result of replacing each separator character | |
## in path with a slash ('/') character. Multiple separators are | |
## replaced by multiple slashes. | |
proc ToSlash*(path: string): string = | |
if Separator == '/': | |
return path | |
return strings_ReplaceAll(path, $Separator, "/") | |
## FromSlash returns the result of replacing each slash ('/') character | |
## in path with a separator character. Multiple slashes are replaced | |
## by multiple separators. | |
proc FromSlash*(path: string): string = | |
if Separator == '/': | |
return path | |
return strings_ReplaceAll(path, "/", $Separator) | |
## SplitList splits a list of paths joined by the OS-specific ListSeparator, | |
## usually found in PATH or GOPATH environment variables. | |
## Unlike strings_Split, SplitList returns an empty slice when passed an empty | |
## string. | |
proc SplitList*(path: string): seq[string] = | |
if OS == "win": | |
return splitList_win(path) | |
elif OS == "posix": | |
return splitList_posix(path) | |
elif defined(plan9): | |
return splitList_plan9(path) | |
proc volumeNameLen(path: string): int = | |
if OS == "win": | |
return volumeNameLen_win(path) | |
elif OS == "posix": | |
return volumeNameLen_posix(path) | |
elif defined(plan9): | |
return volumeNameLen_plan9(path) | |
## Split splits path immediately following the final Separator, | |
## separating it into a directory and file name component. | |
## If there is no Separator in path, Split returns an empty dir | |
## and file set to path. | |
## The returned values have the property that path = dir+file. | |
proc Split*(path: string): tuple[dir, file: string] = | |
var vol = VolumeName(path) | |
var i = len(path) - 1 | |
while i >= len(vol) and not os_IsPathSeparator(path[i]): | |
i -= 1 | |
return (path[0..i], path[(i + 1)..^1]) | |
## Join joins any number of path elements into a single path, | |
## separating them with an OS specific Separator. Empty elements | |
## are ignored. The result is Cleaned. However, if the argument | |
## list is empty or all its elements are empty, Join returns | |
## an empty string. | |
## On Windows, the result will only be a UNC path if the first | |
## non-empty element is a UNC path. | |
proc Join*(elem: varargs[string]): string = | |
if OS == "win": | |
return join_win(elem) | |
elif OS == "posix": | |
return join_posix(elem) | |
elif defined(plan9): | |
return join_plan9(elem) | |
## Ext returns the file name extension used by path. | |
## The extension is the suffix beginning at the final dot | |
## in the final element of path; it is empty if there is | |
## no dot. | |
proc Ext*(path: string): string = | |
var i = len(path) - 1 | |
while i >= 0 and not os_IsPathSeparator(path[i]): | |
if path[i] == '.': | |
return path[i..^1] | |
i -= 1 | |
return "" | |
## EvalSymlinks returns the path name after the evaluation of any symbolic | |
## links. | |
## If path is relative the result will be relative to the current directory, | |
## unless one of the components is an absolute symbolic link. | |
## EvalSymlinks calls Clean on the result. | |
proc EvalSymlinks*(path: string): string = | |
if OS == "win": | |
return evalSymLinks_win(path) | |
elif OS == "posix" or OS == "plan9": | |
return evalSymLinks_posix(path) | |
## Abs returns an absolute representation of path. | |
## If the path is not absolute it will be joined with the current | |
## working directory to turn it into an absolute path. The absolute | |
## path name for a given file is not guaranteed to be unique. | |
## Abs calls Clean on the result. | |
proc Abs*(path: string): string = | |
if OS == "win": | |
return abs_win(path) | |
elif defined(posix) or defined(plan9): | |
return abs_plan9(path) | |
proc IsAbs*(path: string): bool = | |
if OS == "win": | |
return isAbs_win(path) | |
elif OS == "posix": | |
return isAbs_posix(path) | |
elif defined(plan9): | |
return isAbs_plan9(path) | |
proc sameWord(a, b: string): bool = | |
if OS == "win": | |
return sameWord_win(a, b) | |
elif OS == "posix": | |
return sameWord_posix(a, b) | |
elif defined(plan9): | |
return sameWord_plan9(a, b) | |
proc unixAbs(path: string): string = | |
if IsAbs(path): | |
return Clean(path) | |
var wd = os_Getwd() | |
return Join(wd, path) | |
## Rel returns a relative path that is lexically equivalent to targpath when | |
## joined to basepath with an intervening separator. That is, | |
## Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself. | |
## On success, the returned path will always be relative to basepath, | |
## even if basepath and targpath share no elements. | |
## An error is returned if targpath can't be made relative to basepath or if | |
## knowing the current working directory would be necessary to compute it. | |
## Rel calls Clean on the result. | |
proc Rel*(basepath, targpath: string): string = | |
var baseVol = VolumeName(basepath) | |
var targVol = VolumeName(targpath) | |
var base = Clean(basepath) | |
var targ = Clean(targpath) | |
if sameWord(targ, base): | |
return "." | |
base = base[len(baseVol)..^1] | |
targ = targ[len(targVol)..^1] | |
if base == ".": | |
base = "" | |
## Can't use IsAbs - '''\a''' and '''a''' are both relative in Windows. | |
var baseSlashed = len(base) > 0 and base[0] == Separator | |
var targSlashed = len(targ) > 0 and targ[0] == Separator | |
if baseSlashed != targSlashed or not sameWord(baseVol, targVol): | |
raise newException(Exception, "Rel: can't make " & targpath & " relative to " & basepath) | |
## Position base[(b0)..(bi - 1)] and targ[(t0)..(ti - 1)] at the first differing elements. | |
var bl = len(base) | |
var tl = len(targ) | |
var b0, bi, t0, ti: int | |
while true: | |
while bi < bl and base[bi] != Separator: | |
bi += 1 | |
while ti < tl and targ[ti] != Separator: | |
ti += 1 | |
if not sameWord(targ[t0..(ti - 1)], base[b0..(bi - 1)]): | |
break | |
if bi < bl: | |
bi += 1 | |
if ti < tl: | |
ti += 1 | |
b0 = bi | |
t0 = ti | |
if base[b0..(bi - 1)] == "..": | |
raise newException(Exception, "Rel: can't make " & targpath & " relative to " & basepath) | |
if b0 != bl: | |
## Base elements left. Must go up before going down. | |
var seps = strings_Count(base[b0..(bl - 1)], $Separator) | |
var size = 2 + seps * 3 | |
if tl != t0: | |
size += 1 + tl - t0 | |
var buf = newString(size) | |
var n = 2 | |
for i in 0..(seps - 1): | |
buf[n] = Separator | |
copy(buf, (n + 1)..^1, "..", 0..2) | |
n += 3 | |
if t0 != tl: | |
buf[n] = Separator | |
copy(buf, (n + 1)..^1, targ, (t0)..^1) | |
return buf | |
return targ[t0..^1] | |
## SkipDir is used as a return value from WalkFuncs to indicate that | |
## the directory named in the call is to be skipped. It is not returned | |
## as an error by any function. | |
var SkipDir = "skip this directory" | |
## WalkFunc is the type of the function called for each file or directory | |
## visited by Walk. The path argument contains the argument to Walk as a | |
## prefix; that is, if Walk is called with "dir", which is a directory | |
## containing the file "a", the walk function will be called with argument | |
## "dir/a". The info argument is the os_FileInfo for the named path. | |
## | |
## If there was a problem walking to the file or directory named by path, the | |
## incoming error will describe the problem and the function can decide how | |
## to handle that error (and Walk will not descend into that directory). In the | |
## case of an error, the info argument will be nil. If an error is returned, | |
## processing stops. The sole exception is when the function returns the special | |
## value SkipDir. If the function returns SkipDir when invoked on a directory, | |
## Walk skips the directory's contents entirely. If the function returns SkipDir | |
## when invoked on a non-directory file, Walk skips the remaining files in the | |
## containing directory. | |
## walk recursively descends path, calling walkFn. | |
proc walk(path: string, info: os_FileInfo, walkFn: WalkFunc): bool = | |
if not info.IsDir(): | |
return walkFn(path, info, false) | |
var | |
names: seq[string] | |
err: bool | |
try: | |
names = readDirNames(path) | |
except: | |
err = true | |
var err1 = walkFn(path, info, err) | |
## If err != nil, walk can't walk into this directory. | |
## err1 != nil means walkFn want walk to skip this directory or stop walking. | |
## Therefore, if one of err and err1 isn't nil, walk will return. | |
if err or err1: | |
## The caller's behavior is controlled by the return value, which is decided | |
## by walkFn. walkFn may ignore err and return nil. | |
## If walkFn returns SkipDir, it will be handled by the caller. | |
## So walk should return whatever walkFn returns. | |
return err1 | |
for name in names: | |
var filename = Join(path, name) | |
var fileInfo: os_FileInfo | |
try: | |
fileInfo = os_Lstat(filename) | |
except: | |
err = true | |
if err: | |
err = walkFn(filename, fileInfo, err) | |
if err: # and err != SkipDir: | |
return err | |
else: | |
err = walk(filename, fileInfo, walkFn) | |
if err: | |
if not fileInfo.IsDir(): # or err != SkipDir: | |
return err | |
return false | |
## Walk walks the file tree rooted at root, calling walkFn for each file or | |
## directory in the tree, including root. All errors that arise visiting files | |
## and directories are filtered by walkFn. The files are walked in lexical | |
## order, which makes the output deterministic but means that for very | |
## large directories Walk can be inefficient. | |
## Walk does not follow symbolic links. | |
proc Walk*(root: string, walkFn: WalkFunc): bool = | |
var err: bool | |
var info: os_FileInfo | |
try: | |
info = os_Lstat(root) | |
except: | |
err = true | |
if err: | |
err = walkFn(root, nil, err) | |
else: | |
err = walk(root, info, walkFn) | |
if err: # == SkipDir: | |
return false | |
return err | |
## readDirNames reads the directory named by dirname and returns | |
## a sorted list of directory entries. | |
proc readDirNames(dirname: string): seq[string] = | |
var f = os_Open(dirname) | |
var names = f.Readdirnames(-1) | |
f.Close() | |
sort_Strings(names) | |
return names | |
## Base returns the last element of path. | |
## Trailing path separators are removed before extracting the last element. | |
## If the path is empty, Base returns ".". | |
## If the path consists entirely of separators, Base returns a single separator. | |
proc Base*(path_p: string): string = | |
var path = path_p | |
if path == "": | |
return "." | |
## Strip trailing slashes. | |
while len(path) > 0 and os_IsPathSeparator(path[len(path) - 1]): | |
path = path[0..(len(path) - 2)] | |
## Throw away volume name | |
path = path[len(VolumeName(path))..^1] | |
## Find the last element | |
var i = len(path) - 1 | |
while i >= 0 and not os_IsPathSeparator(path[i]): | |
i -= 1 | |
if i >= 0: | |
path = path[(i + 1)..^1] | |
## If empty now, it had only slashes. | |
if path == "": | |
return $Separator | |
return path | |
## Dir returns all but the last element of path, typically the path's directory. | |
## After dropping the final element, Dir calls Clean on the path and trailing | |
## slashes are removed. | |
## If the path is empty, Dir returns ".". | |
## If the path consists entirely of separators, Dir returns a single separator. | |
## The returned path does not end in a separator unless it is the root directory. | |
proc Dir*(path: string): string = | |
var vol = VolumeName(path) | |
var i = len(path) - 1 | |
while i >= len(vol) and not os_IsPathSeparator(path[i]): | |
i -= 1 | |
var dir = Clean(path[len(vol)..i]) | |
if dir == "." and len(vol) > 2: | |
## must be UNC | |
return vol | |
return vol & dir | |
## VolumeName returns leading volume name. | |
## Given "C:\foo\bar" it returns "C:" on Windows. | |
## Given "\\host\share\foo" it returns "\\host\share". | |
## On other platforms it returns "". | |
proc VolumeName*(path: string): string = | |
if OS == "win": | |
return path[0..(volumeNameLen_win(path) - 1)] | |
elif OS == "posix": | |
return path[0..(volumeNameLen_posix(path) - 1)] | |
elif OS == "plan9": | |
return path[0..(volumeNameLen_plan9(path) - 1)] | |
## +build aix darwin dragonfly freebsd js,wasm linux netbsd openbsd solaris | |
# package filepath | |
## IsAbs reports whether the path is absolute. | |
proc isAbs_posix*(path_p: string): bool = | |
return strings_HasPrefix(path_p, "/") | |
proc HasPrefix*(p, prefix: string): bool = | |
if OS == "win": | |
return HasPrefix_win(p, prefix) | |
else: | |
return HasPrefix_posix(p, prefix) | |
## volumeNameLen returns length of the leading volume name on Windows. | |
## It returns 0 elsewhere. | |
proc volumeNameLen_posix(path: string): int = | |
return 0 | |
## HasPrefix exists for historical compatibility and should not be used. | |
## | |
## Deprecated: HasPrefix does not respect path boundaries and | |
## does not ignore case when required. | |
proc HasPrefix_posix*(p, prefix: string): bool = | |
return strings_HasPrefix(p, prefix) | |
proc splitList_posix(path: string): seq[string] = | |
if path == "": | |
return @[] | |
return strings_Split(path, $ListSeparator) | |
proc join_posix(elem: openarray[string]): string = | |
## If there's a bug here, fix the logic in ./path_plan9.go too. | |
for i, e in elem: | |
if e != "": | |
return Clean(strings_Join(elem[i..^1], $Separator)) | |
return "" | |
proc sameWord_posix(a, b: string): bool = | |
return a == b | |
# package filepath | |
proc walkSymlinks(path_p: string): string = | |
var path = path_p | |
var volLen = volumeNameLen(path) | |
var pathSeparator = $os_PathSeparator | |
if volLen < len(path) and os_IsPathSeparator(path[volLen]): | |
volLen += 1 | |
var vol = path[0..(volLen - 1)] | |
var dest = vol | |
var linksWalked = 0 | |
var (start, ends) = (volLen, volLen) | |
while start < len(path): | |
while start < len(path) and os_IsPathSeparator(path[start]): | |
start += 1 | |
ends = start | |
while ends < len(path) and not os_IsPathSeparator(path[ends]): | |
ends += 1 | |
start = ends | |
## On Windows, "." can be a symlink. | |
## We look it up, and use the value if it is absolute. | |
## If not, we just return ".". | |
var isWindowsDot = OS == "windows" and path[volumeNameLen(path)..^1] == "." | |
## The next path component is in path[start..(end - 1)]. | |
if ends == start: | |
## No more path components. | |
break | |
elif path[start..(ends - 1)] == "." and not isWindowsDot: | |
## Ignore path component ".". | |
continue | |
elif path[start..(ends - 1)] == "..": | |
## Back up to previous component if possible. | |
## Note that volLen includes any leading slash. | |
## Set r to the index of the last slash in dest, | |
## after the volume. | |
var r = len(dest) - 1 | |
while r >= volLen: | |
if os_IsPathSeparator(dest[r]): | |
break | |
r -= 1 | |
if r < volLen or dest[(r + 1)..^1] == "..": | |
## Either path has no slashes | |
## (it's empty or just "C:") | |
## or it ends in a ".." we had to keep. | |
## Either way, keep this "..". | |
if len(dest) > volLen: | |
dest &= pathSeparator | |
dest &= ".." | |
else: | |
## Discard everything since the last slash. | |
dest = dest[0..(r - 1)] | |
continue | |
## Ordinary path component. Add it to result. | |
if len(dest) > volumeNameLen(dest) and not os_IsPathSeparator(dest[len(dest) - 1]): | |
dest &= pathSeparator | |
dest &= path[start..(ends - 1)] | |
## Resolve symlink. | |
var fi = os_Lstat(dest) | |
if true: # fi.Mode() & os_ModeSymlink == 0'i32: | |
if not fi.Mode().IsDir() and ends < len(path): | |
raise newException(Exception, syscall_ENOTDIR) | |
continue | |
## Found symlink. | |
linksWalked += 1 | |
if linksWalked > 255: | |
raise newException(Exception, "EvalSymlinks: too many links") | |
var link: string | |
link = os_Readlink(dest) | |
if isWindowsDot and not IsAbs(link): | |
## On Windows, if "." is a relative symlink, | |
## just return ".". | |
break | |
path = link & path[ends..^1] | |
var v = volumeNameLen(link) | |
if v > 0: | |
## Symlink to drive name is an absolute path. | |
if v < len(link) and os_IsPathSeparator(link[v]): | |
v += 1 | |
vol = link[0..(v - 1)] | |
dest = vol | |
ends = len(vol) | |
elif len(link) > 0 and os_IsPathSeparator(link[0]): | |
## Symlink to absolute path. | |
dest = link[0..(1 - 1)] | |
ends = 1 | |
else: | |
## Symlink to relative path; replace last | |
## path component in dest. | |
var r = len(dest) - 1 | |
while r >= volLen: | |
if os_IsPathSeparator(dest[r]): | |
break | |
r -= 1 | |
if r < volLen: | |
dest = vol | |
else: | |
dest = dest[0..(r - 1)] | |
ends = 0 | |
return Clean(dest) | |
## ErrBadPattern indicates a pattern was malformed. | |
var ErrBadPattern = "syntax error in pattern" | |
## Match reports whether name matches the shell file name pattern. | |
## The pattern syntax is: | |
## | |
## pattern: | |
## { term } | |
## term: | |
## '*' matches any sequence of non-Separator characters | |
## '?' matches any single non-Separator character | |
## '[' [ '^' ] { character-range } ']' | |
## character class (must be non-empty) | |
## c matches character c (c != '*', '?', '\\', '[') | |
## '\\' c matches character c | |
## | |
## character-range: | |
## c matches character c (c != '\\', '-', ']') | |
## '\\' c matches character c | |
## lo '-' hi matches character c for lo <= c <= hi | |
## | |
## Match requires pattern to match all of name, not just a substring. | |
## The only possible returned error is ErrBadPattern, when pattern | |
## is malformed. | |
## | |
## On Windows, escaping is disabled. Instead, '\\' is treated as | |
## path separator. | |
## | |
proc Match*(pattern_p, name_p: string): bool = | |
var pattern = pattern_p | |
var name = name_p | |
while len(pattern) > 0: | |
block Pattern: | |
var star: bool | |
var chunk: string | |
(star, chunk, pattern) = scanChunk(pattern) | |
if star and chunk == "": | |
## Trailing * matches rest of string unless it has a /. | |
return not strings_Contains(name, $Separator) | |
## Look for match at current position. | |
var (t, ok) = matchChunk(chunk, name) | |
## if we're the last chunk, make sure we've exhausted the name | |
## otherwise we'll give a false result even if we could still match | |
## using the star | |
if ok and (len(t) == 0 or len(pattern) > 0): | |
name = t | |
continue | |
# if err != "": | |
# return (false, err) | |
if star: | |
## Look for match skipping i + 1 chars. | |
## Cannot skip /. | |
var i = 0 | |
while i < len(name) and name[i] != Separator: | |
var (t, ok) = matchChunk(chunk, name[i + 1..^1]) | |
if ok: | |
## if we're the last chunk, make sure we exhausted the name | |
if len(pattern) == 0 and len(t) > 0: | |
i += 1 | |
continue | |
name = t | |
break Pattern | |
# if err != "": | |
# return (false, err) | |
i += 1 | |
return false | |
return len(name) == 0 | |
## scanChunk gets the next segment of pattern, which is a non-star string | |
## possibly preceded by a star. | |
proc scanChunk(pattern_p: string): tuple[star: bool, chunk, rest: string] = | |
var pattern = pattern_p | |
while len(pattern) > 0 and pattern[0] == '*': | |
pattern = pattern[1..^1] | |
result.star = true | |
var inrange = false | |
var i = 0 | |
while i < len(pattern): | |
case pattern[i] | |
of '\\': | |
if OS != "windows": | |
## error check handled in matchChunk: bad pattern. | |
if i + 1 < len(pattern): | |
i += 1 | |
of '[': | |
inrange = true | |
of ']': | |
inrange = false | |
of '*': | |
if not inrange: | |
break | |
else: | |
discard | |
i += 1 | |
return (result.star, pattern[0..(i - 1)], pattern[i..^1]) | |
## matchChunk checks whether chunk matches the beginning of s. | |
## If so, it returns the remainder of s (after the match). | |
## Chunk is all single-character operators: literals, char classes, and ?. | |
proc matchChunk(chunk_p, s_p: string): tuple[rest: string, ok: bool] = | |
var chunk = chunk_p | |
var s = s_p | |
while len(chunk) > 0: | |
if len(s) == 0: | |
return | |
case chunk[0] | |
of '[': | |
## character class | |
var (r, n) = utf8_DecodeRuneInString(s) | |
s = s[n..^1] | |
chunk = chunk[1..^1] | |
## We can't end right after '[', we're expecting at least | |
## a closing bracket and possibly a caret. | |
if len(chunk) == 0: | |
raise newException(Exception, ErrBadPattern) | |
## possibly negated | |
var negated = chunk[0] == '^' | |
if negated: | |
chunk = chunk[1..^1] | |
## parse all ranges | |
var match = false | |
var nrange = 0 | |
while true: | |
if len(chunk) > 0 and chunk[0] == ']' and nrange > 0: | |
chunk = chunk[1..^1] | |
break | |
var lo, hi: rune | |
(lo, chunk) = getEsc(chunk) | |
hi = lo | |
if chunk[0] == '-': | |
(hi, chunk) = getEsc(chunk[1..^1]) | |
if lo <= r and r <= hi: | |
match = true | |
nrange += 1 | |
if match == negated: | |
return | |
of '?': | |
if s[0] == Separator: | |
return | |
var (_, n) = utf8_DecodeRuneInString(s) | |
s = s[n..^1] | |
chunk = chunk[1..^1] | |
of '\\': | |
if OS != "windows": | |
chunk = chunk[1..^1] | |
if len(chunk) == 0: | |
raise newException(Exception, ErrBadPattern) | |
if chunk[0] != s[0]: | |
return | |
s = s[1..^1] | |
chunk = chunk[1..^1] | |
else: | |
if chunk[0] != s[0]: | |
return | |
s = s[1..^1] | |
chunk = chunk[1..^1] | |
return (s, true) | |
## getEsc gets a possibly-escaped character from chunk, for a character class. | |
proc getEsc(chunk_p: string): tuple[r: rune, nchunk: string] = | |
var chunk = chunk_p | |
if len(chunk) == 0 or chunk[0] == '-' or chunk[0] == ']': | |
raise newException(Exception, ErrBadPattern) | |
if chunk[0] == '\\' and OS != "windows": | |
chunk = chunk[1..^1] | |
if len(chunk) == 0: | |
raise newException(Exception, ErrBadPattern) | |
var (r, n) = utf8_DecodeRuneInString(chunk) | |
if r == utf8_RuneError and n == 1: | |
raise newException(Exception, ErrBadPattern) | |
result.nchunk = chunk[n..^1] | |
if len(result.nchunk) == 0: | |
raise newException(Exception, ErrBadPattern) | |
return | |
## Glob returns the names of all files matching pattern or nil | |
## if there is no matching file. The syntax of patterns is the same | |
## as in Match. The pattern may describe hierarchical names such as | |
## /usr/*/bin/ed (assuming the Separator is '/'). | |
## | |
## Glob ignores file system errors such as I/O errors reading directories. | |
## The only possible returned error is ErrBadPattern, when pattern | |
## is malformed. | |
proc Glob*(pattern: string): seq[string] = | |
if not hasMeta(pattern): | |
discard os_Lstat(pattern) | |
# if result.err != "": | |
# return (@[], "") | |
return @[pattern] | |
var (dir, file) = Split(pattern) | |
var volumeLen = 0 | |
if OS == "windows": | |
(volumeLen, dir) = cleanGlobPathWindows(dir) | |
else: | |
dir = cleanGlobPath(dir) | |
if not hasMeta(dir[volumeLen..^1]): | |
if OS == "win": | |
return glob_helper(dir, file, @[]) | |
## Prevent infinite recursion. See issue 15879. | |
if dir == pattern: | |
raise newException(Exception, ErrBadPattern) | |
var m = Glob(dir) | |
for _, d in m: | |
result = glob_helper(d, file, result) | |
return | |
## cleanGlobPath prepares path for glob matching. | |
proc cleanGlobPath(path: string): string = | |
case path | |
of "": | |
return "." | |
of $Separator: | |
## do nothing to the path | |
return path | |
else: | |
return path[0..(len(path) - 2)] ## chop off trailing separator | |
## cleanGlobPathWindows is windows version of cleanGlobPath. | |
proc cleanGlobPathWindows(path: string): tuple[prefixLen: int, cleaned: string] = | |
var vollen = volumeNameLen(path) | |
if path == "": | |
return (0, ".") | |
elif vollen + 1 == len(path) and os_IsPathSeparator(path[len(path) - 1]): ## /, \, C:\ and C:/ | |
## do nothing to the path | |
return (vollen + 1, path) | |
elif vollen == len(path) and len(path) == 2: ## C: | |
return (vollen, path & ".") ## convert C: into C:. | |
else: | |
if vollen >= len(path): | |
vollen = len(path) - 1 | |
return (vollen, path[0..(len(path) - 2)]) ## chop off trailing separator | |
## glob searches for files matching pattern in the directory dir | |
## and appends them to matches. If the directory cannot be | |
## opened, it returns the existing matches. New matches are | |
## added in lexicographical order. | |
proc glob_helper(dir, pattern: string, matches: seq[string]): seq[string] = | |
var m = matches | |
var fi = os_Stat(dir) | |
if not fi.IsDir(): | |
return | |
var d: os_File | |
try: | |
d = os_Open(dir) | |
except: | |
return | |
defer: d.Close() | |
var names: seq[string] | |
try: | |
names = d.Readdirnames(-1) | |
except: | |
discard | |
sort_Strings(names) | |
for n in names: | |
var matched = Match(pattern, n) | |
if matched: | |
m.add(Join(dir, n)) | |
return | |
## hasMeta reports whether path contains any of the magic characters | |
## recognized by Match. | |
proc hasMeta(path: string): bool = | |
var magicChars = r"*?[" | |
if OS != "windows": | |
magicChars = r"*?[\" | |
return strings_ContainsAny(path, magicChars) | |
# package filepath | |
## IsAbs reports whether the path is absolute. | |
proc isAbs_plan9*(path_p: string): bool = | |
return strings_HasPrefix(path_p, "/") or strings_HasPrefix(path_p, "#") | |
## volumeNameLen returns length of the leading volume name on Windows. | |
## It returns 0 elsewhere. | |
proc volumeNameLen_plan9(path: string): int = | |
return 0 | |
## HasPrefix exists for historical compatibility and should not be used. | |
## | |
## Deprecated: HasPrefix does not respect path boundaries and | |
## does not ignore case when required. | |
proc HasPrefix_plan9*(p, prefix: string): bool = | |
return strings_HasPrefix(p, prefix) | |
proc splitList_plan9(path: string): seq[string] = | |
if path == "": | |
return @[] | |
return strings_Split(path, $ListSeparator) | |
proc abs_plan9(path_p: string): string = | |
return unixAbs(path_p) | |
proc join_plan9(elem: openarray[string]): string = | |
## If there's a bug here, fix the logic in ./path_unix.go too. | |
for i, e in elem: | |
if e != "": | |
return Clean(strings_Join(elem[i..^1], $Separator)) | |
return "" | |
proc sameWord_plan9(a, b: string): bool = | |
return a == b | |
# package filepath | |
proc isSlash(c: char): bool = | |
return c == '\\' or c == '/' | |
## reservedNames lists reserved Windows names. Search for PRN in | |
## https:##docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file | |
## for details. | |
var reservedNames = @[ | |
"CON", "PRN", "AUX", "NUL", | |
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", | |
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", | |
] | |
## isReservedName returns true, if path is Windows reserved name. | |
## See reservedNames for the full list. | |
proc isReservedName(path: string): bool = | |
if len(path) == 0: | |
return false | |
for reserved in reservedNames: | |
if strings_EqualFold(path, reserved): | |
return true | |
return false | |
## IsAbs reports whether the path is absolute. | |
proc isAbs_win*(path_p: string): bool = | |
var path = path_p | |
if isReservedName(path): | |
return true | |
var l = volumeNameLen(path) | |
if l == 0: | |
return false | |
path = path[l..^1] | |
if path == "": | |
return false | |
return isSlash(path[0]) | |
## volumeNameLen returns length of the leading volume name on Windows. | |
## It returns 0 elsewhere. | |
proc volumeNameLen_win(path: string): int = | |
if len(path) < 2: | |
return 0 | |
# Classic Drive | |
let isDrive = ( | |
path[1] == ':' and | |
(c in 'a'..'z' or c in'A'..'Z') | |
) | |
if isDrive: | |
return 2 | |
## See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/62e862f4-2a51-452e-8eeb-dc4ff5ee33cc | |
let | |
isUNC = ( | |
len(path) >= 5 and # "\\" host-name "\" share-name | |
isSlash(path[0]) and # "\" | |
isSlash(path[1]) and # " \" | |
not isSlash(path[2]) and # A NetBIOS name cannot start with a dot, | |
path[2] != '.' # nor can a FQDN or IP address. | |
) | |
if isUNC: | |
## TODO This doesn't ensure that the UNC is 100% valid | |
## First, find the slash separating the "host-name" and "share-name" | |
var n = -1 | |
for i in 3..(high(path) - 1) | |
if isSlash(path[n]): | |
# If we've found a slash, move to the next character. | |
# We avoid going past the end of string by stopping at `high(path) - 1`. | |
n = i + 1 | |
break | |
if n == -1: | |
return 0 | |
# The share must have at least one character that doesn't start with a | |
# slash or a dot | |
if isSlash(path[n]) or path[n] == '.': | |
return 0 | |
## Second, find the next slash or the end of the string. | |
if not isSlash(path[n]): | |
if path[n] == '.': | |
break | |
for i in n..high(path): | |
if isSlash(path[n]): | |
break | |
n += 1 | |
return n | |
break | |
n += 1 | |
return 0 | |
## HasPrefix exists for historical compatibility and should not be used. | |
## | |
## Deprecated: HasPrefix does not respect path boundaries and | |
## does not ignore case when required. | |
proc HasPrefix_win*(p, prefix: string): bool = | |
if strings_HasPrefix(p, prefix): | |
return true | |
return strings_HasPrefix(strings_ToLower(p), strings_ToLower(prefix)) | |
proc splitList_win(path: string): seq[string] = | |
## The same implementation is used in LookPath in os/exec; | |
## consider changing os/exec when changing this. | |
if path == "": | |
return @[] | |
## Split path, respecting but preserving quotes. | |
var list: seq[string] = @[] | |
var start = 0 | |
var quo = false | |
var i = 0 | |
while i < len(path): | |
case path[i] | |
of '"': | |
quo = not quo | |
of ListSeparator: | |
if not quo: | |
list.add(path[start..(i - 1)]) | |
start = i + 1 | |
else: | |
discard | |
i += 1 | |
list.add(path[start..^1]) | |
## Remove quotes. | |
for i, s in list: | |
list[i] = strings_ReplaceAll(s, "\"", "'") | |
return list | |
proc abs_win(path_p: string): string = | |
var path = path_p | |
if path == "": | |
## syscall_FullPath returns an error on empty path, because it's not a valid path. | |
## To implement Abs behavior of returning working directory on empty string input, | |
## special-case empty path by changing it to "." path. See golang.org/issue/24441. | |
path = "." | |
var fullPath = syscall_FullPath(path) | |
return Clean(fullPath) | |
proc join_win(elem: openarray[string]): string = | |
for i, e in elem: | |
if e != "": | |
return joinNonEmpty(elem[i..^1]) | |
return "" | |
## joinNonEmpty is like join, but it assumes that the first element is non-empty. | |
proc joinNonEmpty(elem: openarray[string]): string = | |
if len(elem[0]) == 2 and elem[0][1] == ':': | |
## First element is drive letter without terminating slash. | |
## Keep path relative to current directory on that drive. | |
## Skip empty elements. | |
var i = 1 | |
while i < len(elem): | |
if elem[i] != "": | |
break | |
i += 1 | |
return Clean(elem[0] & strings_Join(elem[i..^1], $Separator)) | |
## The following logic prevents Join from inadvertently creating a | |
## UNC path on Windows. Unless the first element is a UNC path, Join | |
## shouldn't create a UNC path. See golang.org/issue/9167. | |
var p = Clean(strings_Join(elem, $Separator)) | |
if not isUNC(p): | |
return p | |
## p == UNC only allowed when the first element is a UNC path. | |
var head = Clean(elem[0]) | |
if isUNC(head): | |
return p | |
## head + tail == UNC, but joining two non-UNC paths should not result | |
## in a UNC path. Undo creation of UNC path. | |
var tail = Clean(strings_Join(elem[1..^1], $Separator)) | |
if head[len(head) - 1] == Separator: | |
return head & tail | |
return head & $Separator & tail | |
## isUNC reports whether path is a UNC path. | |
proc isUNC(path: string): bool = | |
return volumeNameLen(path) > 2 | |
proc sameWord_win(a, b: string): bool = | |
return strings_EqualFold(a, b) | |
## +build not windows | |
# package filepath | |
proc evalSymlinks_posix(path: string): string = | |
return walkSymlinks(path) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
try: | |
import regex | |
except: | |
print("Requires regex and possibly Python 3") | |
sys.exit(1) | |
LINE_START=r'^\t*' | |
WORD=r'\w+' | |
WORD_LIST=rf'{WORD}(?: *, *{WORD})+' | |
LINE_END=r' *$' | |
L_PAREN=r'\(' | |
R_PAREN=r'\)' | |
L_CBRACE=r'\{' | |
R_CBRACE=r'\}' | |
L_SQBRACE=r'\[' | |
R_SQBRACE=r'\]' | |
PAREN_PAIR_2=rf'(?P<PAREN_PAIR>{L_PAREN}((?>[^{L_PAREN}{R_PAREN}]+|(?&PAREN_PAIR))*){R_PAREN})' | |
CBRACE_PAIR_2=rf'(?P<CBRACE_PAIR>{L_CBRACE}((?>[^{L_CBRACE}{R_CBRACE}]+|(?&CBRACE_PAIR))*){R_CBRACE})' | |
BREAK=rf'\b' | |
STAR='\*' | |
TAB='\t' | |
subs = [ | |
## Variable Transforms ## | |
# Single short variable declaration | |
fr'({LINE_START})({WORD}) *:=', | |
'{1}var {2} =', | |
# Multiple short variable declarations | |
rf'({LINE_START})({WORD_LIST}) *:=', | |
'{1}var ({2}) =', | |
# Single long variable declaration | |
rf'({LINE_START})var +({WORD}) +({WORD})', | |
'{1}var {2}: {3}', | |
# Multiple long variable declarations | |
rf'({LINE_START})var +({WORD_LIST}) +({WORD})', | |
'{1}var {2}: {3}', | |
# Multiple variable assignments | |
rf'({LINE_START})({WORD_LIST}) *=', | |
'{1}({2}) =', | |
## Function Transforms ## | |
# Method to Go func | |
rf'({LINE_START})func *{PAREN_PAIR_2} *({WORD}) *{L_PAREN}', | |
'{1}func {4} ({3}, ', | |
# Function argument to Nim argument | |
rf'({LINE_START}func +{WORD} *{L_PAREN}[^{R_PAREN}]*?){BREAK}({WORD}) +([^, \)]+)', | |
'{1}{2}: {3}', | |
# Function argument with reference to Nim argument | |
rf'({LINE_START}func +({WORD}) *{L_PAREN}[^{R_PAREN}]*?){BREAK}({WORD}) +{STAR} *({WORD})', | |
'{1}{2}: ref {4}', | |
# Function with no arguments and no return value | |
rf'({LINE_START})func +({WORD}) *{L_PAREN}{R_PAREN} *{L_CBRACE}{LINE_END}', | |
'{1}proc {2} =', | |
# Function with arguments and no return value | |
rf'({LINE_START})func +({WORD}) *{PAREN_PAIR_2} *{L_CBRACE}{LINE_END}', | |
'{1}proc {2}({4}) =', | |
# Function with no arguments and a return value | |
rf'({LINE_START})func +({WORD}) *{L_PAREN}{R_PAREN} *(.+?) *{L_CBRACE}{LINE_END}', | |
'{1}proc {2}(): {3} =', | |
# Function with arguments and a return value | |
rf'({LINE_START})func +({WORD}) *{PAREN_PAIR_2} *(.+?) *{L_CBRACE}{LINE_END}', | |
'{1}proc {2}({4}): {5} =', | |
# Add export marker to proc | |
rf'({LINE_START})proc +([A-Z]({WORD})?) *\(', | |
'{1}proc {2}*(', | |
## Operators & Symbols | |
# Comments | |
rf'//', | |
'##', | |
# Boolean And | |
rf'&&', | |
'and', | |
# Boolean Or | |
rf'\|\|', | |
'or', | |
# Boolean Negation | |
rf'!({WORD})', | |
'not {1}', | |
# Increment | |
rf'\+\+', | |
' += 1', | |
# Decrement | |
rf'--', | |
' -= 1', | |
# Slice with two arguments | |
rf'{L_SQBRACE} *([^{L_SQBRACE}{R_SQBRACE}: ]+?) *: *([^{L_SQBRACE}{R_SQBRACE}: ]+?) *{R_SQBRACE}', | |
'[({1})\.\.({2} - 1)]', | |
# Slice with right argument | |
rf'{L_SQBRACE} *: *([^{L_SQBRACE}{R_SQBRACE}: ]+?) *{R_SQBRACE}', | |
'[0\.\.({1} - 1)]', | |
# Slice with left argument | |
rf'{L_SQBRACE} *([^{L_SQBRACE}{R_SQBRACE}: ]+?) *: *{R_SQBRACE}', | |
'[({1})\.\.^1]', | |
# Remove redundant maths | |
rf'\+ *1 *- *1', | |
'', | |
rf'- *1 *- *1', | |
'- 2', | |
# Backticks to triple-quoted strings | |
'`', | |
rf"'''", | |
## Statements ## | |
# for statement to while true | |
rf'({LINE_START})for *{L_CBRACE}{LINE_END}', | |
'{1}while true:', | |
# if/for statement | |
rf'({LINE_START})((if|for) .*?) *{L_CBRACE}{LINE_END}', | |
'{1}{2}:', | |
# else statement | |
rf'({LINE_START}){R_CBRACE} *else *{L_CBRACE}{LINE_END}', | |
'{1}else:', | |
# Return statement | |
rf'({LINE_START})return +([^{L_PAREN}{R_PAREN}].*?,.*[^{L_PAREN}{R_PAREN}]){LINE_END}', | |
'{1}return ({2})', | |
# Case statement | |
rf'({LINE_START})case ', | |
'{1}of ', | |
# Switch statement | |
rf'({LINE_START})switch +(.*) *{L_CBRACE}{LINE_END}', | |
'{1}case {2}', | |
# Package statement | |
rf'({LINE_START})(package .*{LINE_END})', | |
'# {2}', | |
## Function Calls ## | |
rf'fmt\.Println', | |
'echo', | |
rf'{L_SQRACE}{R_SQRACE}({WORD}){L_CBRACE}{R_CBRACE}', | |
'newSeq[{1}]()', | |
] | |
def sub_overlap(pattern, repl, string, count=0, flags=0): | |
print("Replacing:") | |
print(f"{TAB}{pattern}") | |
print("With:") | |
print(f"{TAB}{repl}") | |
print("Captures:") | |
changed = True | |
replacements_made = 0 | |
def replacement_func(m): | |
nonlocal changed, replacements_made | |
changed = True | |
replacements_made += 1 | |
if len(m.groups()) > 0: | |
print(f"{TAB}{m.groups()}") | |
return m.expandf(repl) | |
while changed: | |
changed = False | |
string = regex.sub(pattern, replacement_func, string, count, flags) | |
if replacements_made == 0: | |
print("Warning: No replacements made.") | |
else: | |
print(f"Replacements made: {replacements_made}") | |
print() | |
return string | |
def main(): | |
data = sys.stdin.read() | |
i = 0 | |
while i < len(subs): | |
find = subs[i] | |
i += 1 | |
replace = subs[i] | |
i += 1 | |
try: | |
data = sub_overlap(find, replace, data, 0, regex.M + regex.V1) | |
except: | |
print() | |
print('\t', find) | |
print('\t', replace) | |
print() | |
raise | |
sys.stdout.write(data) | |
main() | |
# ## Structures ## | |
# # Slice Literals | |
# {L_SQBRACE}{R_SQBRACE} *{WORD} *{CBRACE_PAIR_2} | |
# ({LINE_START})func (.*) +\{{LINE_END} | |
# $1proc $2 = | |
# !(\w+) | |
# not $1 | |
# fmt\.Println | |
# echo | |
# ^(\t*proc .+\))( *\w+) +={LINE_END} | |
# $1:$2 = | |
# (proc .+?)(?!if|or)(\b\w+) +(string|int|bool|error|func|byte) | |
# $1$2: $3 | |
# ({LINE_START})type +(\w+) +struct +\{{LINE_END} | |
# $1type $2 = object | |
# ({LINE_START})for +\{{LINE_END} | |
# $1while true: | |
# ({LINE_START})(if|for) +([^\{]+) +\{{LINE_END} | |
# $1$2 $3: | |
# ({LINE_START})\} +else +\{{LINE_END} | |
# $1else: | |
# \[\](byte|string|char|int|bool) | |
# seq[$1] | |
# (^\t*)return ([^,\(#]+(, [^,\(#]+)+){LINE_END} | |
# $1return ($2) | |
# if err != nil: | |
# if err != "": | |
# :\] | |
# ..^1] | |
# \[:([^\]]+) | |
# [..$1-1 | |
# case | |
# of | |
# ({LINE_START})switch(.*) +\{{LINE_END} | |
# $1case $2: | |
# ({LINE_START})(proc.*\)) *\((.*)\) | |
# $1$2: tuple[$3] | |
# ({LINE_START})var (\w+) (\w+){LINE_END} | |
# $1var $2: $3 | |
# runtime\.GOOS | |
# OS | |
# string\(Separator\) | |
# $Separator | |
# ({LINE_START})\} else if(.*?) *\{$ | |
# $1elif | |
# tuple\[(\w+), error\] | |
# tuple[r: $1, err: error] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment