diff options
Diffstat (limited to 'lib/pure/os.nim')
-rw-r--r-- | lib/pure/os.nim | 199 |
1 files changed, 116 insertions, 83 deletions
diff --git a/lib/pure/os.nim b/lib/pure/os.nim index f53abe81d..48d255dca 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -41,7 +41,7 @@ type OSErrorCode* = distinct int32 ## Specifies an OS Error Code. -{.deprecated: [FReadEnv: ReadEnvEffect, FWriteEnv: WriteEnvEffect, +{.deprecated: [FReadEnv: ReadEnvEffect, FWriteEnv: WriteEnvEffect, FReadDir: ReadDirEffect, FWriteDir: WriteDirEffect, TOSErrorCode: OSErrorCode @@ -81,7 +81,7 @@ when defined(Nimdoc): # only for proper documentation: ## Windows. FileSystemCaseSensitive* = true - ## True if the file system is case sensitive, false otherwise. Used by + ## true if the file system is case sensitive, false otherwise. Used by ## `cmpPaths` to compare filenames properly. ExeExt* = "" @@ -261,7 +261,7 @@ proc osErrorMsg*(errorCode: OSErrorCode): string = if errorCode != OSErrorCode(0'i32): result = $os.strerror(errorCode.int32) -proc raiseOSError*(errorCode: OSErrorCode) = +proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} = ## Raises an ``OSError`` exception. The ``errorCode`` will determine the ## message, ``osErrorMsg`` will be used to get this message. ## @@ -271,7 +271,10 @@ proc raiseOSError*(errorCode: OSErrorCode) = ## the message ``unknown OS error`` will be used. var e: ref OSError; new(e) e.errorCode = errorCode.int32 - e.msg = osErrorMsg(errorCode) + if additionalInfo.len == 0: + e.msg = osErrorMsg(errorCode) + else: + e.msg = additionalInfo & " " & osErrorMsg(errorCode) if e.msg == "": e.msg = "unknown OS error" raise e @@ -359,7 +362,7 @@ when defined(windows): template wrapBinary(varname, winApiProc, arg, arg2: expr) {.immediate.} = var varname = winApiProc(newWideCString(arg), arg2) - proc findFirstFile(a: string, b: var TWIN32_FIND_DATA): THandle = + proc findFirstFile(a: string, b: var WIN32_FIND_DATA): Handle = result = findFirstFileW(newWideCString(a), b) template findNextFile(a, b: expr): expr = findNextFileW(a, b) template getCommandLine(): expr = getCommandLineW() @@ -373,7 +376,7 @@ when defined(windows): template getFilename(f: expr): expr = $f.cFilename - proc skipFindData(f: TWIN32_FIND_DATA): bool {.inline.} = + proc skipFindData(f: WIN32_FIND_DATA): bool {.inline.} = # Note - takes advantage of null delimiter in the cstring const dot = ord('.') result = f.cFileName[0].int == dot and (f.cFileName[1].int == 0 or @@ -390,7 +393,7 @@ proc existsFile*(filename: string): bool {.rtl, extern: "nos$1", if a != -1'i32: result = (a and FILE_ATTRIBUTE_DIRECTORY) == 0'i32 else: - var res: TStat + var res: Stat return stat(filename, res) >= 0'i32 and S_ISREG(res.st_mode) proc existsDir*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect].} = @@ -404,7 +407,7 @@ proc existsDir*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect] if a != -1'i32: result = (a and FILE_ATTRIBUTE_DIRECTORY) != 0'i32 else: - var res: TStat + var res: Stat return stat(dir, res) >= 0'i32 and S_ISDIR(res.st_mode) proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1", @@ -419,7 +422,7 @@ proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1", if a != -1'i32: result = (a and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32 else: - var res: TStat + var res: Stat return lstat(link, res) >= 0'i32 and S_ISLNK(res.st_mode) proc fileExists*(filename: string): bool {.inline.} = @@ -433,11 +436,11 @@ proc dirExists*(dir: string): bool {.inline.} = proc getLastModificationTime*(file: string): Time {.rtl, extern: "nos$1".} = ## Returns the `file`'s last modification time. when defined(posix): - var res: TStat + var res: Stat if stat(file, res) < 0'i32: raiseOSError(osLastError()) return res.st_mtime else: - var f: TWIN32_FIND_DATA + var f: WIN32_FIND_DATA var h = findFirstFile(file, f) if h == -1'i32: raiseOSError(osLastError()) result = winTimeToUnixTime(rdFileTime(f.ftLastWriteTime)) @@ -446,11 +449,11 @@ proc getLastModificationTime*(file: string): Time {.rtl, extern: "nos$1".} = proc getLastAccessTime*(file: string): Time {.rtl, extern: "nos$1".} = ## Returns the `file`'s last read or write access time. when defined(posix): - var res: TStat + var res: Stat if stat(file, res) < 0'i32: raiseOSError(osLastError()) return res.st_atime else: - var f: TWIN32_FIND_DATA + var f: WIN32_FIND_DATA var h = findFirstFile(file, f) if h == -1'i32: raiseOSError(osLastError()) result = winTimeToUnixTime(rdFileTime(f.ftLastAccessTime)) @@ -461,11 +464,11 @@ proc getCreationTime*(file: string): Time {.rtl, extern: "nos$1".} = ## Note that under posix OS's, the returned time may actually be the time at ## which the file's attribute's were last modified. when defined(posix): - var res: TStat + var res: Stat if stat(file, res) < 0'i32: raiseOSError(osLastError()) return res.st_ctime else: - var f: TWIN32_FIND_DATA + var f: WIN32_FIND_DATA var h = findFirstFile(file, f) if h == -1'i32: raiseOSError(osLastError()) result = winTimeToUnixTime(rdFileTime(f.ftCreationTime)) @@ -618,6 +621,20 @@ proc parentDir*(path: string): string {. else: result = "" +proc tailDir*(path: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Returns the tail part of `path`.. + ## + ## | Example: ``tailDir("/usr/local/bin") == "local/bin"``. + ## | Example: ``tailDir("usr/local/bin/") == "local/bin"``. + ## | Example: ``tailDir("bin") == ""``. + var q = 1 + if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2 + for i in 0..len(path)-q: + if path[i] in {DirSep, AltSep}: + return substr(path, i+1) + result = "" + proc isRootDir*(path: string): bool {. noSideEffect, rtl, extern: "nos$1".} = ## Checks whether a given `path` is a root directory @@ -794,27 +811,27 @@ proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} = result = path[0] == '/' when defined(Windows): - proc openHandle(path: string, followSymlink=true): THandle = + proc openHandle(path: string, followSymlink=true): Handle = var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL if not followSymlink: flags = flags or FILE_FLAG_OPEN_REPARSE_POINT when useWinUnicode: result = createFileW( - newWideCString(path), 0'i32, + newWideCString(path), 0'i32, FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, flags, 0 ) else: result = createFileA( - path, 0'i32, + path, 0'i32, FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, flags, 0 ) proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect].} = - ## Returns True if both pathname arguments refer to the same physical + ## Returns true if both pathname arguments refer to the same physical ## file or directory. Raises an exception if any of the files does not ## exist or information about it can not be obtained. ## @@ -827,7 +844,7 @@ proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", var lastErr: OSErrorCode if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE: - var fi1, fi2: TBY_HANDLE_FILE_INFORMATION + var fi1, fi2: BY_HANDLE_FILE_INFORMATION if getFileInformationByHandle(f1, addr(fi1)) != 0 and getFileInformationByHandle(f2, addr(fi2)) != 0: @@ -846,7 +863,7 @@ proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", if not success: raiseOSError(lastErr) else: - var a, b: TStat + var a, b: Stat if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32: raiseOSError(osLastError()) else: @@ -854,7 +871,7 @@ proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = - ## Returns True if both pathname arguments refer to files with identical + ## Returns true if both pathname arguments refer to files with identical ## binary content. const bufSize = 8192 # 8K buffer @@ -903,7 +920,7 @@ proc getFilePermissions*(filename: string): set[FilePermission] {. ## an error. On Windows, only the ``readonly`` flag is checked, every other ## permission is available in any case. when defined(posix): - var a: TStat + var a: Stat if stat(filename, a) < 0'i32: raiseOSError(osLastError()) result = {} if (a.st_mode and S_IRUSR) != 0'i32: result.incl(fpUserRead) @@ -924,11 +941,11 @@ proc getFilePermissions*(filename: string): set[FilePermission] {. var res = getFileAttributesA(filename) if res == -1'i32: raiseOSError(osLastError()) if (res and FILE_ATTRIBUTE_READONLY) != 0'i32: - result = {fpUserExec, fpUserRead, fpGroupExec, fpGroupRead, + result = {fpUserExec, fpUserRead, fpGroupExec, fpGroupRead, fpOthersExec, fpOthersRead} else: result = {fpUserExec..fpOthersRead} - + proc setFilePermissions*(filename: string, permissions: set[FilePermission]) {. rtl, extern: "nos$1", tags: [WriteDirEffect].} = ## sets the file permissions for `filename`. `OSError` is raised in case of @@ -939,15 +956,15 @@ proc setFilePermissions*(filename: string, permissions: set[FilePermission]) {. if fpUserRead in permissions: p = p or S_IRUSR if fpUserWrite in permissions: p = p or S_IWUSR if fpUserExec in permissions: p = p or S_IXUSR - + if fpGroupRead in permissions: p = p or S_IRGRP if fpGroupWrite in permissions: p = p or S_IWGRP if fpGroupExec in permissions: p = p or S_IXGRP - + if fpOthersRead in permissions: p = p or S_IROTH if fpOthersWrite in permissions: p = p or S_IWOTH if fpOthersExec in permissions: p = p or S_IXOTH - + if chmod(filename, p) != 0: raiseOSError(osLastError()) else: when useWinUnicode: @@ -955,7 +972,7 @@ proc setFilePermissions*(filename: string, permissions: set[FilePermission]) {. else: var res = getFileAttributesA(filename) if res == -1'i32: raiseOSError(osLastError()) - if fpUserWrite in permissions: + if fpUserWrite in permissions: res = res and not FILE_ATTRIBUTE_READONLY else: res = res or FILE_ATTRIBUTE_READONLY @@ -1019,7 +1036,7 @@ proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", if moveFileA(source, dest, 0'i32) == 0'i32: raiseOSError(osLastError()) else: if c_rename(source, dest) != 0'i32: - raise newException(OSError, $strerror(errno)) + raiseOSError(osLastError(), $strerror(errno)) when not declared(ENOENT) and not defined(Windows): when NoFakeVars: @@ -1030,11 +1047,11 @@ when not declared(ENOENT) and not defined(Windows): when defined(Windows): when useWinUnicode: template deleteFile(file: expr): expr {.immediate.} = deleteFileW(file) - template setFileAttributes(file, attrs: expr): expr {.immediate.} = + template setFileAttributes(file, attrs: expr): expr {.immediate.} = setFileAttributesW(file, attrs) else: template deleteFile(file: expr): expr {.immediate.} = deleteFileA(file) - template setFileAttributes(file, attrs: expr): expr {.immediate.} = + template setFileAttributes(file, attrs: expr): expr {.immediate.} = setFileAttributesA(file, attrs) proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} = @@ -1047,14 +1064,14 @@ proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} else: let f = file if deleteFile(f) == 0: - if getLastError() == ERROR_ACCESS_DENIED: + if getLastError() == ERROR_ACCESS_DENIED: if setFileAttributes(f, FILE_ATTRIBUTE_NORMAL) == 0: raiseOSError(osLastError()) if deleteFile(f) == 0: raiseOSError(osLastError()) else: if c_remove(file) != 0'i32 and errno != ENOENT: - raise newException(OSError, $strerror(errno)) + raiseOSError(osLastError(), $strerror(errno)) proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", tags: [ExecIOEffect].} = @@ -1220,7 +1237,7 @@ iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} = ## notation is supported. when defined(windows): var - f: TWIN32_FIND_DATA + f: WIN32_FIND_DATA res: int res = findFirstFile(pattern, f) if res != -1: @@ -1232,7 +1249,7 @@ iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} = findClose(res) else: # here we use glob var - f: TGlob + f: Glob res: int f.gl_offs = 0 f.gl_pathc = 0 @@ -1276,7 +1293,7 @@ iterator walkDir*(dir: string): tuple[kind: PathComponent, path: string] {. ## dirA/fileA1.txt ## dirA/fileA2.txt when defined(windows): - var f: TWIN32_FIND_DATA + var f: WIN32_FIND_DATA var h = findFirstFile(dir / "*", f) if h != -1: while true: @@ -1297,14 +1314,16 @@ iterator walkDir*(dir: string): tuple[kind: PathComponent, path: string] {. if x == nil: break var y = $x.d_name if y != "." and y != "..": - var s: TStat + var s: Stat y = dir / y var k = pcFile when defined(linux) or defined(macosx) or defined(bsd): if x.d_type != DT_UNKNOWN: if x.d_type == DT_DIR: k = pcDir - if x.d_type == DT_LNK: k = succ(k) + if x.d_type == DT_LNK: + if dirExists(y): k = pcLinkToDir + else: k = succ(k) yield (k, y) continue @@ -1319,9 +1338,9 @@ iterator walkDirRec*(dir: string, filter={pcFile, pcDir}): string {. ## walks over the directory `dir` and yields for each file in `dir`. The ## full path for each file is returned. ## **Warning**: - ## Modifying the directory structure while the iterator - ## is traversing may result in undefined behavior! - ## + ## Modifying the directory structure while the iterator + ## is traversing may result in undefined behavior! + ## ## Walking is recursive. `filter` controls the behaviour of the iterator: ## ## --------------------- --------------------------------------------- @@ -1424,7 +1443,7 @@ proc createSymlink*(src, dest: string) = ## by `src`. On most operating systems, will fail if a lonk ## ## **Warning**: - ## Some OS's (such as Microsoft Windows) restrict the creation + ## Some OS's (such as Microsoft Windows) restrict the creation ## of symlinks to root users (administrators). when defined(Windows): let flag = dirExists(src).int32 @@ -1444,7 +1463,7 @@ proc createHardlink*(src, dest: string) = ## Create a hard link at `dest` which points to the item specified ## by `src`. ## - ## **Warning**: Most OS's restrict the creation of hard links to + ## **Warning**: Most OS's restrict the creation of hard links to ## root users (administrators) . when defined(Windows): when useWinUnicode: @@ -1548,7 +1567,7 @@ proc parseCmdLine*(c: string): seq[string] {. add(a, c[i]) inc(i) add(result, a) - + proc copyFileWithPermissions*(source, dest: string, ignorePermissionErrors = true) = ## Copies a file from `source` to `dest` preserving file permissions. @@ -1640,6 +1659,22 @@ proc getTempDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect].} = when defined(windows): return string(getEnv("TEMP")) & "\\" else: return "/tmp/" +proc expandSymlink*(symlinkPath: string): string = + ## Returns a string representing the path to which the symbolic link points. + ## + ## On Windows this is a noop, ``symlinkPath`` is simply returned. + when defined(windows): + result = symlinkPath + else: + result = newString(256) + var len = readlink(symlinkPath, result, 256) + if len < 0: + raiseOSError(osLastError()) + if len > 256: + result = newString(len+1) + len = readlink(symlinkPath, result, len) + setLen(result, len) + when defined(nimdoc): # Common forward declaration docstring block for parameter retrieval procs. proc paramCount*(): int {.tags: [ReadIOEffect].} = @@ -1842,7 +1877,7 @@ proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect].} = when defined(windows): winlean.sleep(int32(milsecs)) else: - var a, b: Ttimespec + var a, b: Timespec a.tv_sec = Time(milsecs div 1000) a.tv_nsec = (milsecs mod 1000) * 1000 * 1000 discard posix.nanosleep(a, b) @@ -1851,7 +1886,7 @@ proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = ## returns the file size of `file`. Can raise ``OSError``. when defined(windows): - var a: TWIN32_FIND_DATA + var a: WIN32_FIND_DATA var resA = findFirstFile(file, a) if resA == -1: raiseOSError(osLastError()) result = rdFileSize(a) @@ -1863,25 +1898,7 @@ proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1", close(f) else: raiseOSError(osLastError()) -proc expandTilde*(path: string): string {.tags: [ReadEnvEffect].} - -proc findExe*(exe: string): string {.tags: [ReadDirEffect, ReadEnvEffect].} = - ## Searches for `exe` in the current working directory and then - ## in directories listed in the ``PATH`` environment variable. - ## Returns "" if the `exe` cannot be found. On DOS-like platforms, `exe` - ## is added the `ExeExt <#ExeExt>`_ file extension if it has none. - result = addFileExt(exe, os.ExeExt) - if existsFile(result): return - var path = string(os.getEnv("PATH")) - for candidate in split(path, PathSep): - when defined(windows): - var x = candidate / result - else: - var x = expandTilde(candidate) / result - if existsFile(x): return x - result = "" - -proc expandTilde*(path: string): string = +proc expandTilde*(path: string): string {.tags: [ReadEnvEffect].} = ## Expands a path starting with ``~/`` to a full path. ## ## If `path` starts with the tilde character and is followed by `/` or `\\` @@ -1901,14 +1918,30 @@ proc expandTilde*(path: string): string = else: result = path +proc findExe*(exe: string): string {.tags: [ReadDirEffect, ReadEnvEffect].} = + ## Searches for `exe` in the current working directory and then + ## in directories listed in the ``PATH`` environment variable. + ## Returns "" if the `exe` cannot be found. On DOS-like platforms, `exe` + ## is added the `ExeExt <#ExeExt>`_ file extension if it has none. + result = addFileExt(exe, os.ExeExt) + if existsFile(result): return + var path = string(os.getEnv("PATH")) + for candidate in split(path, PathSep): + when defined(windows): + var x = candidate / result + else: + var x = expandTilde(candidate) / result + if existsFile(x): return x + result = "" + when defined(Windows): type DeviceId* = int32 FileId* = int64 else: type - DeviceId* = TDev - FileId* = Tino + DeviceId* = Dev + FileId* = Ino type FileInfo* = object @@ -1925,7 +1958,7 @@ type template rawToFormalFileInfo(rawInfo, formalInfo): expr = ## Transforms the native file info structure into the one nim uses. ## 'rawInfo' is either a 'TBY_HANDLE_FILE_INFORMATION' structure on Windows, - ## or a 'TStat' structure on posix + ## or a 'Stat' structure on posix when defined(Windows): template toTime(e): expr = winTimeToUnixTime(rdFileTime(e)) template merge(a, b): expr = a or (b shl 32) @@ -1936,10 +1969,10 @@ template rawToFormalFileInfo(rawInfo, formalInfo): expr = formalInfo.lastAccessTime = toTime(rawInfo.ftLastAccessTime) formalInfo.lastWriteTime = toTime(rawInfo.ftLastWriteTime) formalInfo.creationTime = toTime(rawInfo.ftCreationTime) - + # Retrieve basic permissions if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_READONLY) != 0'i32: - formalInfo.permissions = {fpUserExec, fpUserRead, fpGroupExec, + formalInfo.permissions = {fpUserExec, fpUserRead, fpGroupExec, fpGroupRead, fpOthersExec, fpOthersRead} else: result.permissions = {fpUserExec..fpOthersRead} @@ -1953,7 +1986,7 @@ template rawToFormalFileInfo(rawInfo, formalInfo): expr = else: - template checkAndIncludeMode(rawMode, formalMode: expr) = + template checkAndIncludeMode(rawMode, formalMode: expr) = if (rawInfo.st_mode and rawMode) != 0'i32: formalInfo.permissions.incl(formalMode) formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino) @@ -1988,7 +2021,7 @@ proc getFileInfo*(handle: FileHandle): FileInfo = ## is invalid, an error will be thrown. # Done: ID, Kind, Size, Permissions, Link Count when defined(Windows): - var rawInfo: TBY_HANDLE_FILE_INFORMATION + var rawInfo: BY_HANDLE_FILE_INFORMATION # We have to use the super special '_get_osfhandle' call (wrapped above) # To transform the C file descripter to a native file handle. var realHandle = get_osfhandle(handle) @@ -1996,7 +2029,7 @@ proc getFileInfo*(handle: FileHandle): FileInfo = raiseOSError(osLastError()) rawToFormalFileInfo(rawInfo, result) else: - var rawInfo: TStat + var rawInfo: Stat if fstat(handle, rawInfo) < 0'i32: raiseOSError(osLastError()) rawToFormalFileInfo(rawInfo, result) @@ -2008,22 +2041,22 @@ proc getFileInfo*(file: File): FileInfo = proc getFileInfo*(path: string, followSymlink = true): FileInfo = ## Retrieves file information for the file object pointed to by `path`. - ## + ## ## Due to intrinsic differences between operating systems, the information ## contained by the returned `FileInfo` structure will be slightly different ## across platforms, and in some cases, incomplete or inaccurate. - ## + ## ## When `followSymlink` is true, symlinks are followed and the information ## retrieved is information related to the symlink's target. Otherwise, ## information on the symlink itself is retrieved. - ## + ## ## If the information cannot be retrieved, such as when the path doesn't ## exist, or when permission restrictions prevent the program from retrieving ## file information, an error will be thrown. when defined(Windows): - var + var handle = openHandle(path, followSymlink) - rawInfo: TBY_HANDLE_FILE_INFORMATION + rawInfo: BY_HANDLE_FILE_INFORMATION if handle == INVALID_HANDLE_VALUE: raiseOSError(osLastError()) if getFileInformationByHandle(handle, addr rawInfo) == 0: @@ -2031,12 +2064,12 @@ proc getFileInfo*(path: string, followSymlink = true): FileInfo = rawToFormalFileInfo(rawInfo, result) discard closeHandle(handle) else: - var rawInfo: TStat + var rawInfo: Stat if followSymlink: - if lstat(path, rawInfo) < 0'i32: + if stat(path, rawInfo) < 0'i32: raiseOSError(osLastError()) else: - if stat(path, rawInfo) < 0'i32: + if lstat(path, rawInfo) < 0'i32: raiseOSError(osLastError()) rawToFormalFileInfo(rawInfo, result) @@ -2044,7 +2077,7 @@ proc isHidden*(path: string): bool = ## Determines whether a given path is hidden or not. Returns false if the ## file doesn't exist. The given path must be accessible from the current ## working directory of the program. - ## + ## ## On Windows, a file is hidden if the file's 'hidden' attribute is set. ## On Unix-like systems, a file is hidden if it starts with a '.' (period) ## and is not *just* '.' or '..' ' ." |