diff options
-rw-r--r-- | lib/pure/asyncio.nim | 11 | ||||
-rw-r--r-- | lib/pure/os.nim | 179 | ||||
-rw-r--r-- | lib/pure/osproc.nim | 5 | ||||
-rw-r--r-- | lib/pure/scgi.nim | 15 | ||||
-rw-r--r-- | lib/pure/sockets.nim | 134 | ||||
-rw-r--r-- | lib/pure/terminal.nim | 666 | ||||
-rw-r--r-- | lib/windows/winlean.nim | 2 | ||||
-rw-r--r-- | lib/wrappers/gtk/glib2.nim | 8 | ||||
-rw-r--r-- | lib/wrappers/gtk/gtk2.nim | 2 | ||||
-rw-r--r-- | tools/nimgrep.nim | 2 |
10 files changed, 560 insertions, 464 deletions
diff --git a/lib/pure/asyncio.nim b/lib/pure/asyncio.nim index 403401ff1..4ff6e0ced 100644 --- a/lib/pure/asyncio.nim +++ b/lib/pure/asyncio.nim @@ -167,7 +167,7 @@ proc AsyncSocket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM, result = newAsyncSocket() result.socket = socket(domain, typ, protocol, buffered) result.proto = protocol - if result.socket == InvalidSocket: OSError() + if result.socket == InvalidSocket: OSError(OSLastError()) result.socket.setBlocking(false) proc toAsyncSocket*(sock: TSocket, state: TInfo = SockConnected): PAsyncSocket = @@ -349,7 +349,7 @@ proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket, client.sslNeedAccept = false client.info = SockConnected - if c == InvalidSocket: OSError() + if c == InvalidSocket: SocketError(server.socket) c.setBlocking(false) # TODO: Needs to be tested. # deleg.open is set in ``toDelegate``. @@ -423,6 +423,10 @@ proc isConnecting*(s: PAsyncSocket): bool = proc isClosed*(s: PAsyncSocket): bool = ## Determines whether ``s`` has been closed. return s.info == SockClosed +proc isSendDataBuffered*(s: PAsyncSocket): bool = + ## Determines whether ``s`` has data waiting to be sent, i.e. whether this + ## socket's sendBuffer contains data. + return s.sendBuffer.len != 0 proc setHandleWrite*(s: PAsyncSocket, handleWrite: proc (s: PAsyncSocket) {.closure.}) = @@ -638,8 +642,7 @@ when isMainModule: proc testRead(s: PAsyncSocket, no: int) = echo("Reading! " & $no) var data = "" - if not s.readLine(data): - OSError() + if not s.readLine(data): return if data == "": echo("Closing connection. " & $no) s.close() diff --git a/lib/pure/os.nim b/lib/pure/os.nim index bf00afed2..eaa22d351 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -39,6 +39,8 @@ type FWriteDir* = object of FWriteIO ## effect that denotes a write operation to ## the directory structure + TOSErrorCode* = distinct int32 ## Specifies an OS Error Code. + const doslike = defined(windows) or defined(OS2) or defined(DOS) # DOS-like filesystem @@ -171,10 +173,13 @@ const ## The character which separates the base filename from the extension; ## for example, the '.' in ``os.nim``. -proc OSErrorMsg*(): string {.rtl, extern: "nos$1".} = +proc OSErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} = ## Retrieves the operating system's error flag, ``errno``. ## On Windows ``GetLastError`` is checked before ``errno``. ## Returns "" if no error occured. + ## + ## **Deprecated since version 0.9.4**: use the other ``OSErrorMsg`` proc. + result = "" when defined(Windows): var err = GetLastError() @@ -194,17 +199,89 @@ proc OSErrorMsg*(): string {.rtl, extern: "nos$1".} = if errno != 0'i32: result = $os.strerror(errno) -proc OSError*(msg: string = "") {.noinline, rtl, extern: "nos$1".} = +{.push warning[deprecated]: off.} +proc OSError*(msg: string = "") {.noinline, rtl, extern: "nos$1", deprecated.} = ## raises an EOS exception with the given message ``msg``. ## If ``msg == ""``, the operating system's error flag ## (``errno``) is converted to a readable error message. On Windows ## ``GetLastError`` is checked before ``errno``. ## If no error flag is set, the message ``unknown OS error`` is used. + ## + ## **Deprecated since version 0.9.4**: use the other ``OSError`` proc. if len(msg) == 0: var m = OSErrorMsg() raise newException(EOS, if m.len > 0: m else: "unknown OS error") else: raise newException(EOS, msg) +{.pop.} + +proc `==`*(err1, err2: TOSErrorCode): bool {.borrow.} +proc `$`*(err: TOSErrorCode): string {.borrow.} + +proc OSErrorMsg*(errorCode: TOSErrorCode): string = + ## Converts an OS error code into a human readable string. + ## + ## The error code can be retrieved using the ``OSLastError`` proc. + ## + ## If conversion fails, or ``errorCode`` is ``0`` then ``""`` will be + ## returned. + ## + ## On Windows, the ``-d:useWinAnsi`` compilation flag can be used to + ## make this procedure use the non-unicode Win API calls to retrieve the + ## message. + result = "" + when defined(Windows): + if errorCode != TOSErrorCode(0'i32): + when useWinUnicode: + var msgbuf: widecstring + if FormatMessageW(0x00000100 or 0x00001000 or 0x00000200, + nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32: + result = $msgbuf + if msgbuf != nil: LocalFree(cast[pointer](msgbuf)) + else: + var msgbuf: cstring + if FormatMessageA(0x00000100 or 0x00001000 or 0x00000200, + nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32: + result = $msgbuf + if msgbuf != nil: LocalFree(msgbuf) + else: + if errorCode != TOSErrorCode(0'i32): + result = $os.strerror(errorCode.int32) + +proc OSError*(errorCode: TOSErrorCode) = + ## Raises an ``EOS`` exception. The ``errorCode`` will determine the + ## message, ``OSErrorMsg`` will be used to get this message. + ## + ## The error code can be retrieved using the ``OSLastError`` proc. + ## + ## If the error code is ``0`` or an error message could not be retrieved, + ## the message ``unknown OS error`` will be used. + let msg = OSErrorMsg(errorCode) + if msg == "": + raise newException(EOS, "unknown OS error") + else: + raise newException(EOS, msg) + +{.push stackTrace:off.} +proc OSLastError*(): TOSErrorCode = + ## Retrieves the last operating system error code. + ## + ## This procedure is useful in the event when an OS call fails. In that case + ## this procedure will return the error code describing the reason why the + ## OS call failed. The ``OSErrorMsg`` procedure can then be used to convert + ## this code into a string. + ## + ## **Warning**: + ## The behaviour of this procedure varies between Windows and POSIX systems. + ## On Windows some OS calls can reset the error code to ``0`` causing this + ## procedure to return ``0``. It is therefore advised to call this procedure + ## immediately after an OS call fails. On POSIX systems this is not a problem. + + when defined(windows): + result = TOSErrorCode(GetLastError()) + else: + result = TOSErrorCode(errno) +{.pop.} proc UnixToNativePath*(path: string): string {. noSideEffect, rtl, extern: "nos$1".} = @@ -311,12 +388,12 @@ proc getLastModificationTime*(file: string): TTime {.rtl, extern: "nos$1".} = ## Returns the `file`'s last modification time. when defined(posix): var res: TStat - if stat(file, res) < 0'i32: OSError() + if stat(file, res) < 0'i32: OSError(OSLastError()) return res.st_mtime else: var f: TWIN32_Find_Data var h = findfirstFile(file, f) - if h == -1'i32: OSError() + if h == -1'i32: OSError(OSLastError()) result = winTimeToUnixTime(rdFileTime(f.ftLastWriteTime)) findclose(h) @@ -324,12 +401,12 @@ proc getLastAccessTime*(file: string): TTime {.rtl, extern: "nos$1".} = ## Returns the `file`'s last read or write access time. when defined(posix): var res: TStat - if stat(file, res) < 0'i32: OSError() + if stat(file, res) < 0'i32: OSError(OSLastError()) return res.st_atime else: var f: TWIN32_Find_Data var h = findfirstFile(file, f) - if h == -1'i32: OSError() + if h == -1'i32: OSError(OSLastError()) result = winTimeToUnixTime(rdFileTime(f.ftLastAccessTime)) findclose(h) @@ -337,12 +414,12 @@ proc getCreationTime*(file: string): TTime {.rtl, extern: "nos$1".} = ## Returns the `file`'s creation time. when defined(posix): var res: TStat - if stat(file, res) < 0'i32: OSError() + if stat(file, res) < 0'i32: OSError(OSLastError()) return res.st_ctime else: var f: TWIN32_Find_Data var h = findfirstFile(file, f) - if h == -1'i32: OSError() + if h == -1'i32: OSError(OSLastError()) result = winTimeToUnixTime(rdFileTime(f.ftCreationTime)) findclose(h) @@ -358,30 +435,30 @@ proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} = when useWinUnicode: var res = newWideCString("", bufsize) var L = GetCurrentDirectoryW(bufsize, res) - if L == 0'i32: OSError() + if L == 0'i32: OSError(OSLastError()) result = res$L else: result = newString(bufsize) var L = GetCurrentDirectoryA(bufsize, result) - if L == 0'i32: OSError() + if L == 0'i32: OSError(OSLastError()) setLen(result, L) else: result = newString(bufsize) if getcwd(result, bufsize) != nil: setlen(result, c_strlen(result)) else: - OSError() + OSError(OSLastError()) proc setCurrentDir*(newDir: string) {.inline, tags: [].} = ## Sets the `current working directory`:idx:; `EOS` is raised if ## `newDir` cannot been set. when defined(Windows): when useWinUnicode: - if SetCurrentDirectoryW(newWideCString(newDir)) == 0'i32: OSError() + if SetCurrentDirectoryW(newWideCString(newDir)) == 0'i32: OSError(OSLastError()) else: - if SetCurrentDirectoryA(newDir) == 0'i32: OSError() + if SetCurrentDirectoryA(newDir) == 0'i32: OSError(OSLastError()) else: - if chdir(newDir) != 0'i32: OSError() + if chdir(newDir) != 0'i32: OSError(OSLastError()) proc JoinPath*(head, tail: string): string {. noSideEffect, rtl, extern: "nos$1".} = @@ -571,23 +648,23 @@ proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", var res = newWideCString("", bufsize div 2) var L = GetFullPathNameW(newWideCString(filename), bufsize, res, unused) if L <= 0'i32 or L >= bufsize: - OSError() + OSError(OSLastError()) result = res$L else: var unused: cstring result = newString(bufsize) var L = GetFullPathNameA(filename, bufsize, result, unused) - if L <= 0'i32 or L >= bufsize: OSError() + if L <= 0'i32 or L >= bufsize: OSError(OSLastError()) setLen(result, L) elif defined(macosx) or defined(bsd): # On Mac OS X 10.5, realpath does not allocate the buffer on its own var pathBuffer: cstring = newString(pathMax) var resultBuffer = realpath(filename, pathBuffer) - if resultBuffer == nil: OSError() + if resultBuffer == nil: OSError(OSLastError()) result = $resultBuffer else: var res = realpath(filename, nil) - if res == nil: OSError() + if res == nil: OSError(OSLastError()) result = $res c_free(res) @@ -677,6 +754,7 @@ proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", var f1 = OpenHandle(path1) var f2 = OpenHandle(path2) + var lastErr: TOSErrorCode if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE: var fi1, fi2: TBY_HANDLE_FILE_INFORMATION @@ -685,17 +763,21 @@ proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", result = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber and fi1.nFileIndexHigh == fi2.nFileIndexHigh and fi1.nFileIndexLow == fi2.nFileIndexLow - else: success = false - else: success = false + else: + lastErr = OSLastError() + success = false + else: + lastErr = OSLastError() + success = false discard CloseHandle(f1) discard CloseHandle(f2) - if not success: OSError() + if not success: OSError(lastErr) else: var a, b: TStat if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32: - OSError() + OSError(OSLastError()) else: result = a.st_dev == b.st_dev and a.st_ino == b.st_ino @@ -738,17 +820,17 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1", when useWinUnicode: let s = newWideCString(source) let d = newWideCString(dest) - if CopyFileW(s, d, 0'i32) == 0'i32: OSError() + if CopyFileW(s, d, 0'i32) == 0'i32: OSError(OSLastError()) else: - if CopyFileA(source, dest, 0'i32) == 0'i32: OSError() + if CopyFileA(source, dest, 0'i32) == 0'i32: OSError(OSLastError()) else: # generic version of copyFile which works for any platform: const bufSize = 8000 # better for memory manager var d, s: TFile - if not open(s, source): OSError() + if not open(s, source): OSError(OSLastError()) if not open(d, dest, fmWrite): close(s) - OSError() + OSError(OSLastError()) var buf = alloc(bufsize) while True: var bytesread = readBuffer(s, buf, bufsize) @@ -758,7 +840,7 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1", dealloc(buf) close(s) close(d) - OSError() + OSError(OSLastError()) if bytesread != bufSize: break dealloc(buf) close(s) @@ -767,7 +849,8 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1", proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", tags: [FReadIO, FWriteIO].} = ## Moves a file from `source` to `dest`. If this fails, `EOS` is raised. - if crename(source, dest) != 0'i32: OSError() + if crename(source, dest) != 0'i32: + raise newException(EOS, $strerror(errno)) when not defined(ENOENT): var ENOENT {.importc, header: "<errno.h>".}: cint @@ -775,7 +858,8 @@ when not defined(ENOENT): proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [FWriteDir].} = ## Removes the `file`. If this fails, `EOS` is raised. This does not fail ## if the file never existed in the first place. - if cremove(file) != 0'i32 and errno != ENOENT: OSError() + if cremove(file) != 0'i32 and errno != ENOENT: + raise newException(EOS, $strerror(errno)) proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", tags: [FExecIO].} = @@ -907,14 +991,14 @@ proc putEnv*(key, val: string) {.tags: [FWriteEnv].} = indx = high(environment) when defined(unix): if cputenv(environment[indx]) != 0'i32: - OSError() + OSError(OSLastError()) else: when useWinUnicode: var k = newWideCString(key) var v = newWideCString(val) - if SetEnvironmentVariableW(k, v) == 0'i32: OSError() + if SetEnvironmentVariableW(k, v) == 0'i32: OSError(OSLastError()) else: - if SetEnvironmentVariableA(key, val) == 0'i32: OSError() + if SetEnvironmentVariableA(key, val) == 0'i32: OSError(OSLastError()) iterator envPairs*(): tuple[key, value: TaintedString] {.tags: [FReadEnv].} = ## Iterate over all `environments variables`:idx:. In the first component @@ -1044,10 +1128,12 @@ proc rawRemoveDir(dir: string) = wrapUnary(res, RemoveDirectoryW, dir) else: var res = RemoveDirectoryA(dir) - if res == 0'i32 and GetLastError() != 3'i32 and - GetLastError() != 18'i32: OSError() + let lastError = OSLastError() + if res == 0'i32 and lastError.int32 != 3'i32 and + lastError.int32 != 18'i32 and lastError.int32 != 2'i32: + OSError(lastError) else: - if rmdir(dir) != 0'i32 and errno != ENOENT: OSError() + if rmdir(dir) != 0'i32 and errno != ENOENT: OSError(OSLastError()) proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [ FWriteDir, FReadDir].} = @@ -1065,14 +1151,14 @@ proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [ proc rawCreateDir(dir: string) = when defined(unix): if mkdir(dir, 0o711) != 0'i32 and errno != EEXIST: - OSError() + OSError(OSLastError()) else: when useWinUnicode: wrapUnary(res, CreateDirectoryW, dir) else: var res = CreateDirectoryA(dir) if res == 0'i32 and GetLastError() != 183'i32: - OSError() + OSError(OSLastError()) proc createDir*(dir: string) {.rtl, extern: "nos$1", tags: [FWriteDir].} = ## Creates the `directory`:idx: `dir`. @@ -1213,7 +1299,7 @@ proc getFilePermissions*(filename: string): set[TFilePermission] {. ## permission is available in any case. when defined(posix): var a: TStat - if stat(filename, a) < 0'i32: OSError() + if stat(filename, a) < 0'i32: OSError(OSLastError()) result = {} if (a.st_mode and S_IRUSR) != 0'i32: result.incl(fpUserRead) if (a.st_mode and S_IWUSR) != 0'i32: result.incl(fpUserWrite) @@ -1231,7 +1317,7 @@ proc getFilePermissions*(filename: string): set[TFilePermission] {. wrapUnary(res, GetFileAttributesW, filename) else: var res = GetFileAttributesA(filename) - if res == -1'i32: OSError() + if res == -1'i32: OSError(OSLastError()) if (res and FILE_ATTRIBUTE_READONLY) != 0'i32: result = {fpUserExec, fpUserRead, fpGroupExec, fpGroupRead, fpOthersExec, fpOthersRead} @@ -1257,13 +1343,13 @@ proc setFilePermissions*(filename: string, permissions: set[TFilePermission]) {. if fpOthersWrite in permissions: p = p or S_IWOTH if fpOthersExec in permissions: p = p or S_IXOTH - if chmod(filename, p) != 0: OSError() + if chmod(filename, p) != 0: OSError(OSLastError()) else: when useWinUnicode: wrapUnary(res, GetFileAttributesW, filename) else: var res = GetFileAttributesA(filename) - if res == -1'i32: OSError() + if res == -1'i32: OSError(OSLastError()) if fpUserWrite in permissions: res = res and not FILE_ATTRIBUTE_READONLY else: @@ -1272,7 +1358,7 @@ proc setFilePermissions*(filename: string, permissions: set[TFilePermission]) {. wrapBinary(res2, SetFileAttributesW, filename, res) else: var res2 = SetFileAttributesA(filename, res) - if res2 == - 1'i32: OSError() + if res2 == - 1'i32: OSError(OSLastError()) proc inclFilePermissions*(filename: string, permissions: set[TFilePermission]) {. @@ -1368,6 +1454,9 @@ when defined(macosx): proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [FReadIO].} = ## Returns the filename of the application's executable. + ## + ## This procedure will resolve symlinks. + ## ## **Note**: This does not work reliably on BSD. # Linux: /proc/<pid>/exe @@ -1397,6 +1486,8 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [FReadIO].} = result = newString(int(size)) if getExecPath2(result, size): result = "" # error! + if result.len > 0: + result = result.expandFilename else: # little heuristic that may work on other POSIX-like systems: result = string(getEnv("_")) @@ -1443,7 +1534,7 @@ proc getFileSize*(file: string): biggestInt {.rtl, extern: "nos$1", when defined(windows): var a: TWin32FindData var resA = findfirstFile(file, a) - if resA == -1: OSError() + if resA == -1: OSError(OSLastError()) result = rdFileSize(a) findclose(resA) else: @@ -1451,7 +1542,7 @@ proc getFileSize*(file: string): biggestInt {.rtl, extern: "nos$1", if open(f, file): result = getFileSize(f) close(f) - else: OSError() + else: OSError(OSLastError()) proc findExe*(exe: string): string {.tags: [FReadDir, FReadEnv].} = ## Searches for `exe` in the current working directory and then diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index aa4fbe32d..48a559c4e 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -370,6 +370,7 @@ when defined(Windows) and not defined(useNimRtl): else: success = winlean.CreateProcessA(nil, cmdl, nil, nil, 1, NORMAL_PRIORITY_CLASS, e, wd, SI, ProcInfo) + let lastError = OSLastError() if poParentStreams notin options: FileClose(si.hStdInput) @@ -379,7 +380,7 @@ when defined(Windows) and not defined(useNimRtl): if e != nil: dealloc(e) dealloc(cmdl) - if success == 0: OSError() + if success == 0: OSError(lastError) # Close the handle now so anyone waiting is woken: discard closeHandle(procInfo.hThread) result.FProcessHandle = procInfo.hProcess @@ -450,7 +451,7 @@ when defined(Windows) and not defined(useNimRtl): var res = winlean.CreateProcessA(nil, command, nil, nil, 0, NORMAL_PRIORITY_CLASS, nil, nil, SI, ProcInfo) if res == 0: - OSError() + OSError(OSLastError()) else: Process = ProcInfo.hProcess discard CloseHandle(ProcInfo.hThread) diff --git a/lib/pure/scgi.nim b/lib/pure/scgi.nim index 57fa0b144..8e45032c8 100644 --- a/lib/pure/scgi.nim +++ b/lib/pure/scgi.nim @@ -175,6 +175,15 @@ proc recvBufferAsync(client: PAsyncClient, L: int): TReadLineResult = if ret == L: return ReadFullLine +proc checkCloseSocket(client: PAsyncClient) = + if not client.c.isClosed: + if client.c.isSendDataBuffered: + client.c.setHandleWrite do (s: PAsyncSocket): + if not s.isClosed and not s.isSendDataBuffered: + s.close() + s.delHandleWrite() + else: client.c.close() + proc handleClientRead(client: PAsyncClient, s: PAsyncScgiState) = case client.mode of ClientReadChar: @@ -206,7 +215,7 @@ proc handleClientRead(client: PAsyncClient, s: PAsyncScgiState) = client.mode = ClientReadContent else: s.handleRequest(client.c, client.input, client.headers) - if not client.c.isClosed: client.c.close() + checkCloseSocket(client) of ReadPartialLine, ReadDisconnected, ReadNone: return of ClientReadContent: let L = parseInt(client.headers["CONTENT_LENGTH"])-client.input.len @@ -215,11 +224,11 @@ proc handleClientRead(client: PAsyncClient, s: PAsyncScgiState) = case ret of ReadFullLine: s.handleRequest(client.c, client.input, client.headers) - if not client.c.isClosed: client.c.close() + checkCloseSocket(client) of ReadPartialLine, ReadDisconnected, ReadNone: return else: s.handleRequest(client.c, client.input, client.headers) - if not client.c.isClosed: client.c.close() + checkCloseSocket(client) proc handleAccept(sock: PAsyncSocket, s: PAsyncScgiState) = var client: PAsyncSocket diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim index 603fd612b..33df72d38 100644 --- a/lib/pure/sockets.nim +++ b/lib/pure/sockets.nim @@ -231,7 +231,7 @@ when defined(ssl): if err == 0: raise newException(ESSL, "No error reported.") if err == -1: - OSError() + OSError(OSLastError()) var errStr = ErrErrorString(err, nil) raise newException(ESSL, $errStr) @@ -347,24 +347,23 @@ proc SocketError*(socket: TSocket, err: int = -1, async = false) = else: SSLError("Unknown Error") if err == -1 and not (when defined(ssl): socket.isSSL else: false): + let lastError = OSLastError() if async: when defined(windows): - # TODO: Test on Windows - var err = WSAGetLastError() - if err == WSAEWOULDBLOCK: + if lastError.int32 == WSAEWOULDBLOCK: return - else: OSError() + else: OSError(lastError) else: - if errno == EAGAIN or errno == EWOULDBLOCK: + if lastError.int32 == EAGAIN or lastError.int32 == EWOULDBLOCK: return - else: OSError() - else: OSError() + else: OSError(lastError) + else: OSError(lastError) proc listen*(socket: TSocket, backlog = SOMAXCONN) {.tags: [FReadIO].} = ## Marks ``socket`` as accepting connections. ## ``Backlog`` specifies the maximum length of the ## queue of pending connections. - if listen(socket.fd, cint(backlog)) < 0'i32: OSError() + if listen(socket.fd, cint(backlog)) < 0'i32: OSError(OSLastError()) proc invalidIp4(s: string) {.noreturn, noinline.} = raise newException(EInvalidValue, "invalid ip4 address: " & s) @@ -403,7 +402,7 @@ template gaiNim(a, p, h, list: expr): stmt = var gaiResult = getAddrInfo(a, $p, addr(h), list) if gaiResult != 0'i32: when defined(windows): - OSError() + OSError(OSLastError()) else: OSError($gai_strerror(gaiResult)) @@ -423,7 +422,7 @@ proc bindAddr*(socket: TSocket, port = TPort(0), address = "") {. name.sin_addr.s_addr = sockets.htonl(INADDR_ANY) if bindSocket(socket.fd, cast[ptr TSockAddr](addr(name)), sizeof(name).TSockLen) < 0'i32: - OSError() + OSError(OSLastError()) else: var hints: TAddrInfo var aiList: ptr TAddrInfo = nil @@ -432,21 +431,7 @@ proc bindAddr*(socket: TSocket, port = TPort(0), address = "") {. hints.ai_protocol = toInt(IPPROTO_TCP) gaiNim(address, port, hints, aiList) if bindSocket(socket.fd, aiList.ai_addr, aiList.ai_addrLen.TSockLen) < 0'i32: - OSError() - -when false: - proc bindAddr*(socket: TSocket, port = TPort(0)) = - ## binds a port number to a socket. - var name: Tsockaddr_in - when defined(Windows): - name.sin_family = int16(ord(AF_INET)) - else: - name.sin_family = posix.AF_INET - name.sin_port = sockets.htons(int16(port)) - name.sin_addr.s_addr = sockets.htonl(INADDR_ANY) - if bindSocket(cint(socket), cast[ptr TSockAddr](addr(name)), - sizeof(name).TSockLen) < 0'i32: - OSError() + OSError(OSLastError()) proc getSockName*(socket: TSocket): TPort = ## returns the socket's associated port number. @@ -460,7 +445,7 @@ proc getSockName*(socket: TSocket): TPort = var namelen = sizeof(name).TSockLen if getsockname(socket.fd, cast[ptr TSockAddr](addr(name)), addr(namelen)) == -1'i32: - OSError() + OSError(OSLastError()) result = TPort(sockets.ntohs(name.sin_port)) template acceptAddrPlain(noClientRet, successRet: expr, @@ -472,26 +457,25 @@ template acceptAddrPlain(noClientRet, successRet: expr, addr(addrLen)) if sock < 0: - # TODO: Test on Windows. + let err = OSLastError() when defined(windows): - var err = WSAGetLastError() - if err == WSAEINPROGRESS: + if err.int32 == WSAEINPROGRESS: client = InvalidSocket address = "" when noClientRet.int == -1: return else: return noClientRet - else: OSError() + else: OSError(err) else: - if errno == EAGAIN or errno == EWOULDBLOCK: + if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK: client = InvalidSocket address = "" when noClientRet.int == -1: return else: return noClientRet - else: OSError() + else: OSError(err) else: client.fd = sock client.isBuffered = server.isBuffered @@ -644,7 +628,7 @@ proc getServByName*(name, proto: string): TServent {.tags: [FReadIO].} = var s = winlean.getservbyname(name, proto) else: var s = posix.getservbyname(name, proto) - if s == nil: OSError() + if s == nil: OSError(OSLastError()) result.name = $s.s_name result.aliases = cstringArrayToSeq(s.s_aliases) result.port = TPort(s.s_port) @@ -656,7 +640,7 @@ proc getServByPort*(port: TPort, proto: string): TServent {.tags: [FReadIO].} = var s = winlean.getservbyport(ze(int16(port)).cint, proto) else: var s = posix.getservbyport(ze(int16(port)).cint, proto) - if s == nil: OSError() + if s == nil: OSError(OSLastError()) result.name = $s.s_name result.aliases = cstringArrayToSeq(s.s_aliases) result.port = TPort(s.s_port) @@ -670,7 +654,7 @@ proc getHostByAddr*(ip: string): THostEnt {.tags: [FReadIO].} = when defined(windows): var s = winlean.gethostbyaddr(addr(myaddr), sizeof(myaddr).cuint, cint(sockets.AF_INET)) - if s == nil: OSError() + if s == nil: OSError(OSLastError()) else: var s = posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).TSockLen, cint(posix.AF_INET)) @@ -687,7 +671,7 @@ proc getHostByAddr*(ip: string): THostEnt {.tags: [FReadIO].} = elif s.h_addrtype == posix.AF_INET6: result.addrType = AF_INET6 else: - OSError("unknown h_addrtype") + raise newException(EOS, "unknown h_addrtype") result.addrList = cstringArrayToSeq(s.h_addr_list) result.length = int(s.h_length) @@ -697,7 +681,7 @@ proc getHostByName*(name: string): THostEnt {.tags: [FReadIO].} = var s = winlean.gethostbyname(name) else: var s = posix.gethostbyname(name) - if s == nil: OSError() + if s == nil: OSError(OSLastError()) result.name = $s.h_name result.aliases = cstringArrayToSeq(s.h_aliases) when defined(windows): @@ -708,7 +692,7 @@ proc getHostByName*(name: string): THostEnt {.tags: [FReadIO].} = elif s.h_addrtype == posix.AF_INET6: result.addrType = AF_INET6 else: - OSError("unknown h_addrtype") + raise newException(EOS, "unknown h_addrtype") result.addrList = cstringArrayToSeq(s.h_addr_list) result.length = int(s.h_length) @@ -719,7 +703,7 @@ proc getSockOptInt*(socket: TSocket, level, optname: int): int {. var size = sizeof(res).TSockLen if getsockopt(socket.fd, cint(level), cint(optname), addr(res), addr(size)) < 0'i32: - OSError() + OSError(OSLastError()) result = int(res) proc setSockOptInt*(socket: TSocket, level, optname, optval: int) {. @@ -728,7 +712,7 @@ proc setSockOptInt*(socket: TSocket, level, optname, optval: int) {. var value = cint(optval) if setsockopt(socket.fd, cint(level), cint(optname), addr(value), sizeof(value).TSockLen) < 0'i32: - OSError() + OSError(OSLastError()) proc connect*(socket: TSocket, address: string, port = TPort(0), af: TDomain = AF_INET) {.tags: [FReadIO].} = @@ -746,15 +730,17 @@ proc connect*(socket: TSocket, address: string, port = TPort(0), gaiNim(address, port, hints, aiList) # try all possibilities: var success = false + var lastError: TOSErrorCode var it = aiList while it != nil: if connect(socket.fd, it.ai_addr, it.ai_addrlen.TSockLen) == 0'i32: success = true break + else: lastError = OSLastError() it = it.ai_next freeaddrinfo(aiList) - if not success: OSError() + if not success: OSError(lastError) when defined(ssl): if socket.isSSL: @@ -807,6 +793,7 @@ proc connectAsync*(socket: TSocket, name: string, port = TPort(0), gaiNim(name, port, hints, aiList) # try all possibilities: var success = false + var lastError: TOSErrorCode var it = aiList while it != nil: var ret = connect(socket.fd, it.ai_addr, it.ai_addrlen.TSockLen) @@ -814,22 +801,21 @@ proc connectAsync*(socket: TSocket, name: string, port = TPort(0), success = true break else: - # TODO: Test on Windows. + lastError = OSLastError() when defined(windows): - var err = WSAGetLastError() # Windows EINTR doesn't behave same as POSIX. - if err == WSAEWOULDBLOCK: + if lastError.int32 == WSAEWOULDBLOCK: success = true break else: - if errno == EINTR or errno == EINPROGRESS: + if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS: success = true break it = it.ai_next freeaddrinfo(aiList) - if not success: OSError() + if not success: OSError(lastError) when defined(ssl): if socket.isSSL: socket.sslNoHandshake = true @@ -1110,7 +1096,7 @@ proc waitFor(socket: TSocket, waited: var float, timeout, size: int, var s = @[socket] var startTime = epochTime() let selRet = select(s, timeout - int(waited * 1000.0)) - if selRet < 0: OSError() + if selRet < 0: OSError(OSLastError()) if selRet != 1: raise newException(ETimeout, "Call to '" & funcName & "' timed out.") waited += (epochTime() - startTime) @@ -1259,14 +1245,14 @@ proc readLine*(socket: TSocket, line: var TaintedString, timeout = -1) {. var c: char discard waitFor(socket, waited, timeout, 1, "readLine") var n = recv(socket, addr(c), 1) - if n < 0: OSError() + if n < 0: OSError(OSLastError()) elif n == 0: return if c == '\r': discard waitFor(socket, waited, timeout, 1, "readLine") n = peekChar(socket, c) if n > 0 and c == '\L': discard recv(socket, addr(c), 1) - elif n <= 0: OSError() + elif n <= 0: OSError(OSLastError()) addNlIfEmpty() return elif c == '\L': @@ -1326,6 +1312,7 @@ proc readLineAsync*(socket: TSocket, while true: var c: char var n = recv(socket, addr(c), 1) + #echo(n) if n < 0: if line.len == 0: errorOrNone else: return ReadPartialLine elif n == 0: @@ -1352,7 +1339,7 @@ proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO], deprecated.} = var pos = 0 while true: var bytesRead = recv(socket, addr(string(result)[pos]), bufSize-1) - if bytesRead == -1: OSError() + if bytesRead == -1: OSError(OSLastError()) setLen(result.string, pos + bytesRead) if bytesRead != bufSize-1: break # increase capacity: @@ -1364,7 +1351,7 @@ proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO], deprecated.} = while true: var bytesRead = recv(socket, cstring(buf), bufSize-1) # Error - if bytesRead == -1: OSError() + if bytesRead == -1: OSError(OSLastError()) buf[bytesRead] = '\0' # might not be necessary setLen(buf, bytesRead) @@ -1421,16 +1408,15 @@ proc recvAsync*(socket: TSocket, s: var TaintedString): bool {. else: SSLError("Unknown Error") if bytesRead == -1 and not (when defined(ssl): socket.isSSL else: false): + let err = OSLastError() when defined(windows): - # TODO: Test on Windows - var err = WSAGetLastError() - if err == WSAEWOULDBLOCK: + if err.int32 == WSAEWOULDBLOCK: return False - else: OSError() + else: OSError(err) else: - if errno == EAGAIN or errno == EWOULDBLOCK: + if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK: return False - else: OSError() + else: OSError(err) setLen(s.string, pos + bytesRead) if bytesRead != bufSize-1: break @@ -1475,16 +1461,15 @@ proc recvFromAsync*(socket: TSocket, data: var String, length: int, result = true var callRes = recvFrom(socket, data, length, address, port, flags) if callRes < 0: + let err = OSLastError() when defined(windows): - # TODO: Test on Windows - var err = WSAGetLastError() - if err == WSAEWOULDBLOCK: + if err.int32 == WSAEWOULDBLOCK: return False - else: OSError() + else: OSError(err) else: - if errno == EAGAIN or errno == EWOULDBLOCK: + if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK: return False - else: OSError() + else: OSError(err) proc skip*(socket: TSocket) {.tags: [FReadIO], deprecated.} = ## skips all the data that is pending for the socket @@ -1531,7 +1516,7 @@ proc send*(socket: TSocket, data: string) {.tags: [FWriteIO].} = if socket.isSSL: SSLError() - OSError() + OSError(OSLastError()) proc sendAsync*(socket: TSocket, data: string): int {.tags: [FWriteIO].} = ## sends data to a non-blocking socket. @@ -1561,16 +1546,15 @@ proc sendAsync*(socket: TSocket, data: string): int {.tags: [FWriteIO].} = else: return if result == -1: + let err = OSLastError() when defined(windows): - var err = WSAGetLastError() - # TODO: Test on windows. - if err == WSAEINPROGRESS: + if err.int32 == WSAEINPROGRESS: return 0 - else: OSError() + else: OSError(err) else: - if errno == EAGAIN or errno == EWOULDBLOCK: + if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK: return 0 - else: OSError() + else: OSError(err) proc trySend*(socket: TSocket, data: string): bool {.tags: [FWriteIO].} = @@ -1626,15 +1610,15 @@ proc setBlocking(s: TSocket, blocking: bool) = when defined(Windows): var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking if ioctlsocket(TWinSocket(s.fd), FIONBIO, addr(mode)) == -1: - OSError() + OSError(OSLastError()) else: # BSD sockets var x: int = fcntl(s.fd, F_GETFL, 0) if x == -1: - OSError() + OSError(OSLastError()) else: var mode = if blocking: x and not O_NONBLOCK else: x or O_NONBLOCK if fcntl(s.fd, F_SETFL, mode) == -1: - OSError() + OSError(OSLastError()) s.nonblocking = not blocking proc connect*(socket: TSocket, address: string, port = TPort(0), timeout: int, @@ -1665,6 +1649,6 @@ proc getFD*(socket: TSocket): cint = return socket.fd when defined(Windows): var wsa: TWSADATA - if WSAStartup(0x0101'i16, wsa) != 0: OSError() + if WSAStartup(0x0101'i16, addr wsa) != 0: OSError(OSLastError()) diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index 3be6088ed..9b69bbaa4 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -1,312 +1,310 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module contains a few procedures to control the *terminal* -## (also called *console*). On UNIX, the implementation simply uses ANSI escape -## sequences and does not depend on any other module, on Windows it uses the -## Windows API. -## Changing the style is permanent even after program termination! Use the -## code ``system.addQuitProc(resetAttributes)`` to restore the defaults. - -import macros - -when defined(windows): - import windows, os - - var - conHandle: THandle - # = createFile("CONOUT$", GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0) - - block: - var hTemp = GetStdHandle(STD_OUTPUT_HANDLE) - if DuplicateHandle(GetCurrentProcess(), hTemp, GetCurrentProcess(), - addr(conHandle), 0, 1, DUPLICATE_SAME_ACCESS) == 0: - OSError() - - proc getCursorPos(): tuple [x,y: int] = - var c: TCONSOLE_SCREEN_BUFFER_INFO - if GetConsoleScreenBufferInfo(conHandle, addr(c)) == 0: OSError() - return (int(c.dwCursorPosition.x), int(c.dwCursorPosition.y)) - - proc getAttributes(): int16 = - var c: TCONSOLE_SCREEN_BUFFER_INFO - # workaround Windows bugs: try several times - if GetConsoleScreenBufferInfo(conHandle, addr(c)) != 0: - return c.wAttributes - else: - OSError() - return 0x70'i16 # ERROR: return white background, black text - - var - oldAttr = getAttributes() - -proc setCursorPos*(x, y: int) = - ## sets the terminal's cursor to the (x,y) position. (0,0) is the - ## upper left of the screen. - when defined(windows): - var c: TCoord - c.x = int16(x) - c.y = int16(y) - if SetConsoleCursorPosition(conHandle, c) == 0: OSError() - else: - stdout.write("\e[" & $y & ';' & $x & 'f') - -proc setCursorXPos*(x: int) = - ## sets the terminal's cursor to the x position. The y position is - ## not changed. - when defined(windows): - var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO - var hStdout = conHandle - if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() - var origin = scrbuf.dwCursorPosition - origin.x = int16(x) - if SetConsoleCursorPosition(conHandle, origin) == 0: OSError() - else: - stdout.write("\e[" & $x & 'G') - -when defined(windows): - proc setCursorYPos*(y: int) = - ## sets the terminal's cursor to the y position. The x position is - ## not changed. **Warning**: This is not supported on UNIX! - when defined(windows): - var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO - var hStdout = conHandle - if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() - var origin = scrbuf.dwCursorPosition - origin.y = int16(y) - if SetConsoleCursorPosition(conHandle, origin) == 0: OSError() - else: - nil - -proc CursorUp*(count=1) = - ## Moves the cursor up by `count` rows. - when defined(windows): - var p = getCursorPos() - dec(p.y, count) - setCursorPos(p.x, p.y) - else: - stdout.write("\e[" & $count & 'A') - -proc CursorDown*(count=1) = - ## Moves the cursor down by `count` rows. - when defined(windows): - var p = getCursorPos() - inc(p.y, count) - setCursorPos(p.x, p.y) - else: - stdout.write("\e[" & $count & 'B') - -proc CursorForward*(count=1) = - ## Moves the cursor forward by `count` columns. - when defined(windows): - var p = getCursorPos() - inc(p.x, count) - setCursorPos(p.x, p.y) - else: - stdout.write("\e[" & $count & 'C') - -proc CursorBackward*(count=1) = - ## Moves the cursor backward by `count` columns. - when defined(windows): - var p = getCursorPos() - dec(p.x, count) - setCursorPos(p.x, p.y) - else: - stdout.write("\e[" & $count & 'D') - -when true: - nil -else: - proc EraseLineEnd* = - ## Erases from the current cursor position to the end of the current line. - when defined(windows): - nil - else: - stdout.write("\e[K") - - proc EraseLineStart* = - ## Erases from the current cursor position to the start of the current line. - when defined(windows): - nil - else: - stdout.write("\e[1K") - - proc EraseDown* = - ## Erases the screen from the current line down to the bottom of the screen. - when defined(windows): - nil - else: - stdout.write("\e[J") - - proc EraseUp* = - ## Erases the screen from the current line up to the top of the screen. - when defined(windows): - nil - else: - stdout.write("\e[1J") - -proc EraseLine* = - ## Erases the entire current line. - when defined(windows): - var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO - var numwrote: DWORD - var hStdout = conHandle - if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() - var origin = scrbuf.dwCursorPosition - origin.x = 0'i16 - if SetConsoleCursorPosition(conHandle, origin) == 0: OSError() - var ht = scrbuf.dwSize.Y - origin.Y - var wt = scrbuf.dwSize.X - origin.X - if FillConsoleOutputCharacter(hStdout,' ', ht*wt, - origin, addr(numwrote)) == 0: - OSError() - if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, ht * wt, - scrbuf.dwCursorPosition, addr(numwrote)) == 0: - OSError() - else: - stdout.write("\e[2K") - setCursorXPos(0) - -proc EraseScreen* = - ## Erases the screen with the background colour and moves the cursor to home. - when defined(windows): - var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO - var numwrote: DWORD - var origin: TCoord # is inititalized to 0, 0 - var hStdout = conHandle - if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() - if FillConsoleOutputCharacter(hStdout, ' ', scrbuf.dwSize.X*scrbuf.dwSize.Y, - origin, addr(numwrote)) == 0: - OSError() - if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, - scrbuf.dwSize.X * scrbuf.dwSize.Y, - origin, addr(numwrote)) == 0: - OSError() - setCursorXPos(0) - else: - stdout.write("\e[2J") - -proc ResetAttributes* {.noconv.} = - ## resets all attributes; it is advisable to register this as a quit proc - ## with ``system.addQuitProc(resetAttributes)``. - when defined(windows): - discard SetConsoleTextAttribute(conHandle, oldAttr) - else: - stdout.write("\e[0m") - -type - TStyle* = enum ## different styles for text output - styleBright = 1, ## bright text - styleDim, ## dim text - styleUnknown, ## unknown - styleUnderscore = 4, ## underscored text - styleBlink, ## blinking/bold text - styleReverse = 7, ## unknown - styleHidden ## hidden text - -when not defined(windows): - var - # XXX: These better be thread-local - gFG = 0 - gBG = 0 - -proc setStyle*(style: set[TStyle]) = - ## sets the terminal style - when defined(windows): - var a = 0'i16 - if styleBright in style: a = a or int16(FOREGROUND_INTENSITY) - if styleBlink in style: a = a or int16(BACKGROUND_INTENSITY) - if styleReverse in style: a = a or 0x4000'i16 # COMMON_LVB_REVERSE_VIDEO - if styleUnderscore in style: a = a or 0x8000'i16 # COMMON_LVB_UNDERSCORE - discard SetConsoleTextAttribute(conHandle, a) - else: - for s in items(style): - stdout.write("\e[" & $ord(s) & 'm') - -proc WriteStyled*(txt: string, style: set[TStyle] = {styleBright}) = - ## writes the text `txt` in a given `style`. - when defined(windows): - var old = getAttributes() - setStyle(style) - stdout.write(txt) - discard SetConsoleTextAttribute(conHandle, old) - else: - setStyle(style) - stdout.write(txt) - resetAttributes() - if gFG != 0: - stdout.write("\e[" & $ord(gFG) & 'm') - if gBG != 0: - stdout.write("\e[" & $ord(gBG) & 'm') - -type - TForegroundColor* = enum ## terminal's foreground colors - fgBlack = 30, ## black - fgRed, ## red - fgGreen, ## green - fgYellow, ## yellow - fgBlue, ## blue - fgMagenta, ## magenta - fgCyan, ## cyan - fgWhite ## white - - TBackgroundColor* = enum ## terminal's background colors - bgBlack = 40, ## black - bgRed, ## red - bgGreen, ## green - bgYellow, ## yellow - bgBlue, ## blue - bgMagenta, ## magenta - bgCyan, ## cyan - bgWhite ## white - -proc setForegroundColor*(fg: TForegroundColor, bright=false) = - ## sets the terminal's foreground color - when defined(windows): - var old = getAttributes() and not 0x0007 - if bright: - old = old or FOREGROUND_INTENSITY - const lookup: array [TForegroundColor, int] = [ - 0, - (FOREGROUND_RED), - (FOREGROUND_GREEN), - (FOREGROUND_RED or FOREGROUND_GREEN), - (FOREGROUND_BLUE), - (FOREGROUND_RED or FOREGROUND_BLUE), - (FOREGROUND_BLUE or FOREGROUND_GREEN), - (FOREGROUND_BLUE or FOREGROUND_GREEN or FOREGROUND_RED)] - discard SetConsoleTextAttribute(conHandle, toU16(old or lookup[fg])) - else: - gFG = ord(fg) - if bright: inc(gFG, 60) - stdout.write("\e[" & $gFG & 'm') - -proc setBackgroundColor*(bg: TBackgroundColor, bright=false) = - ## sets the terminal's background color - when defined(windows): - var old = getAttributes() and not 0x0070 - if bright: - old = old or BACKGROUND_INTENSITY - const lookup: array [TBackgroundColor, int] = [ - 0, - (BACKGROUND_RED), - (BACKGROUND_GREEN), - (BACKGROUND_RED or BACKGROUND_GREEN), - (BACKGROUND_BLUE), - (BACKGROUND_RED or BACKGROUND_BLUE), - (BACKGROUND_BLUE or BACKGROUND_GREEN), - (BACKGROUND_BLUE or BACKGROUND_GREEN or BACKGROUND_RED)] - discard SetConsoleTextAttribute(conHandle, toU16(old or lookup[bg])) - else: - gBG = ord(bg) - if bright: inc(gBG, 60) - stdout.write("\e[" & $gBG & 'm') +# +# +# Nimrod's Runtime Library +# (c) Copyright 2012 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module contains a few procedures to control the *terminal* +## (also called *console*). On UNIX, the implementation simply uses ANSI escape +## sequences and does not depend on any other module, on Windows it uses the +## Windows API. +## Changing the style is permanent even after program termination! Use the +## code ``system.addQuitProc(resetAttributes)`` to restore the defaults. + +import macros + +when defined(windows): + import windows, os + + var + conHandle: THandle + # = createFile("CONOUT$", GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0) + + block: + var hTemp = GetStdHandle(STD_OUTPUT_HANDLE) + if DuplicateHandle(GetCurrentProcess(), hTemp, GetCurrentProcess(), + addr(conHandle), 0, 1, DUPLICATE_SAME_ACCESS) == 0: + OSError() + + proc getCursorPos(): tuple [x,y: int] = + var c: TCONSOLE_SCREEN_BUFFER_INFO + if GetConsoleScreenBufferInfo(conHandle, addr(c)) == 0: OSError() + return (int(c.dwCursorPosition.x), int(c.dwCursorPosition.y)) + + proc getAttributes(): int16 = + var c: TCONSOLE_SCREEN_BUFFER_INFO + # workaround Windows bugs: try several times + if GetConsoleScreenBufferInfo(conHandle, addr(c)) != 0: + return c.wAttributes + return 0x70'i16 # ERROR: return white background, black text + + var + oldAttr = getAttributes() + +proc setCursorPos*(x, y: int) = + ## sets the terminal's cursor to the (x,y) position. (0,0) is the + ## upper left of the screen. + when defined(windows): + var c: TCoord + c.x = int16(x) + c.y = int16(y) + if SetConsoleCursorPosition(conHandle, c) == 0: OSError() + else: + stdout.write("\e[" & $y & ';' & $x & 'f') + +proc setCursorXPos*(x: int) = + ## sets the terminal's cursor to the x position. The y position is + ## not changed. + when defined(windows): + var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO + var hStdout = conHandle + if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() + var origin = scrbuf.dwCursorPosition + origin.x = int16(x) + if SetConsoleCursorPosition(conHandle, origin) == 0: OSError() + else: + stdout.write("\e[" & $x & 'G') + +when defined(windows): + proc setCursorYPos*(y: int) = + ## sets the terminal's cursor to the y position. The x position is + ## not changed. **Warning**: This is not supported on UNIX! + when defined(windows): + var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO + var hStdout = conHandle + if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() + var origin = scrbuf.dwCursorPosition + origin.y = int16(y) + if SetConsoleCursorPosition(conHandle, origin) == 0: OSError() + else: + nil + +proc CursorUp*(count=1) = + ## Moves the cursor up by `count` rows. + when defined(windows): + var p = getCursorPos() + dec(p.y, count) + setCursorPos(p.x, p.y) + else: + stdout.write("\e[" & $count & 'A') + +proc CursorDown*(count=1) = + ## Moves the cursor down by `count` rows. + when defined(windows): + var p = getCursorPos() + inc(p.y, count) + setCursorPos(p.x, p.y) + else: + stdout.write("\e[" & $count & 'B') + +proc CursorForward*(count=1) = + ## Moves the cursor forward by `count` columns. + when defined(windows): + var p = getCursorPos() + inc(p.x, count) + setCursorPos(p.x, p.y) + else: + stdout.write("\e[" & $count & 'C') + +proc CursorBackward*(count=1) = + ## Moves the cursor backward by `count` columns. + when defined(windows): + var p = getCursorPos() + dec(p.x, count) + setCursorPos(p.x, p.y) + else: + stdout.write("\e[" & $count & 'D') + +when true: + nil +else: + proc EraseLineEnd* = + ## Erases from the current cursor position to the end of the current line. + when defined(windows): + nil + else: + stdout.write("\e[K") + + proc EraseLineStart* = + ## Erases from the current cursor position to the start of the current line. + when defined(windows): + nil + else: + stdout.write("\e[1K") + + proc EraseDown* = + ## Erases the screen from the current line down to the bottom of the screen. + when defined(windows): + nil + else: + stdout.write("\e[J") + + proc EraseUp* = + ## Erases the screen from the current line up to the top of the screen. + when defined(windows): + nil + else: + stdout.write("\e[1J") + +proc EraseLine* = + ## Erases the entire current line. + when defined(windows): + var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO + var numwrote: DWORD + var hStdout = conHandle + if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() + var origin = scrbuf.dwCursorPosition + origin.x = 0'i16 + if SetConsoleCursorPosition(conHandle, origin) == 0: OSError() + var ht = scrbuf.dwSize.Y - origin.Y + var wt = scrbuf.dwSize.X - origin.X + if FillConsoleOutputCharacter(hStdout,' ', ht*wt, + origin, addr(numwrote)) == 0: + OSError() + if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, ht * wt, + scrbuf.dwCursorPosition, addr(numwrote)) == 0: + OSError() + else: + stdout.write("\e[2K") + setCursorXPos(0) + +proc EraseScreen* = + ## Erases the screen with the background colour and moves the cursor to home. + when defined(windows): + var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO + var numwrote: DWORD + var origin: TCoord # is inititalized to 0, 0 + var hStdout = conHandle + if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() + if FillConsoleOutputCharacter(hStdout, ' ', scrbuf.dwSize.X*scrbuf.dwSize.Y, + origin, addr(numwrote)) == 0: + OSError() + if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, + scrbuf.dwSize.X * scrbuf.dwSize.Y, + origin, addr(numwrote)) == 0: + OSError() + setCursorXPos(0) + else: + stdout.write("\e[2J") + +proc ResetAttributes* {.noconv.} = + ## resets all attributes; it is advisable to register this as a quit proc + ## with ``system.addQuitProc(resetAttributes)``. + when defined(windows): + discard SetConsoleTextAttribute(conHandle, oldAttr) + else: + stdout.write("\e[0m") + +type + TStyle* = enum ## different styles for text output + styleBright = 1, ## bright text + styleDim, ## dim text + styleUnknown, ## unknown + styleUnderscore = 4, ## underscored text + styleBlink, ## blinking/bold text + styleReverse = 7, ## unknown + styleHidden ## hidden text + +when not defined(windows): + var + # XXX: These better be thread-local + gFG = 0 + gBG = 0 + +proc setStyle*(style: set[TStyle]) = + ## sets the terminal style + when defined(windows): + var a = 0'i16 + if styleBright in style: a = a or int16(FOREGROUND_INTENSITY) + if styleBlink in style: a = a or int16(BACKGROUND_INTENSITY) + if styleReverse in style: a = a or 0x4000'i16 # COMMON_LVB_REVERSE_VIDEO + if styleUnderscore in style: a = a or 0x8000'i16 # COMMON_LVB_UNDERSCORE + discard SetConsoleTextAttribute(conHandle, a) + else: + for s in items(style): + stdout.write("\e[" & $ord(s) & 'm') + +proc WriteStyled*(txt: string, style: set[TStyle] = {styleBright}) = + ## writes the text `txt` in a given `style`. + when defined(windows): + var old = getAttributes() + setStyle(style) + stdout.write(txt) + discard SetConsoleTextAttribute(conHandle, old) + else: + setStyle(style) + stdout.write(txt) + resetAttributes() + if gFG != 0: + stdout.write("\e[" & $ord(gFG) & 'm') + if gBG != 0: + stdout.write("\e[" & $ord(gBG) & 'm') + +type + TForegroundColor* = enum ## terminal's foreground colors + fgBlack = 30, ## black + fgRed, ## red + fgGreen, ## green + fgYellow, ## yellow + fgBlue, ## blue + fgMagenta, ## magenta + fgCyan, ## cyan + fgWhite ## white + + TBackgroundColor* = enum ## terminal's background colors + bgBlack = 40, ## black + bgRed, ## red + bgGreen, ## green + bgYellow, ## yellow + bgBlue, ## blue + bgMagenta, ## magenta + bgCyan, ## cyan + bgWhite ## white + +proc setForegroundColor*(fg: TForegroundColor, bright=false) = + ## sets the terminal's foreground color + when defined(windows): + var old = getAttributes() and not 0x0007 + if bright: + old = old or FOREGROUND_INTENSITY + const lookup: array [TForegroundColor, int] = [ + 0, + (FOREGROUND_RED), + (FOREGROUND_GREEN), + (FOREGROUND_RED or FOREGROUND_GREEN), + (FOREGROUND_BLUE), + (FOREGROUND_RED or FOREGROUND_BLUE), + (FOREGROUND_BLUE or FOREGROUND_GREEN), + (FOREGROUND_BLUE or FOREGROUND_GREEN or FOREGROUND_RED)] + discard SetConsoleTextAttribute(conHandle, toU16(old or lookup[fg])) + else: + gFG = ord(fg) + if bright: inc(gFG, 60) + stdout.write("\e[" & $gFG & 'm') + +proc setBackgroundColor*(bg: TBackgroundColor, bright=false) = + ## sets the terminal's background color + when defined(windows): + var old = getAttributes() and not 0x0070 + if bright: + old = old or BACKGROUND_INTENSITY + const lookup: array [TBackgroundColor, int] = [ + 0, + (BACKGROUND_RED), + (BACKGROUND_GREEN), + (BACKGROUND_RED or BACKGROUND_GREEN), + (BACKGROUND_BLUE), + (BACKGROUND_RED or BACKGROUND_BLUE), + (BACKGROUND_BLUE or BACKGROUND_GREEN), + (BACKGROUND_BLUE or BACKGROUND_GREEN or BACKGROUND_RED)] + discard SetConsoleTextAttribute(conHandle, toU16(old or lookup[bg])) + else: + gBG = ord(bg) + if bright: inc(gBG, 60) + stdout.write("\e[" & $gBG & 'm') proc isatty*(f: TFile): bool = ## returns true if `f` is associated with a terminal device. @@ -318,33 +316,33 @@ proc isatty*(f: TFile): bool = importc: "_isatty", header: "<io.h>".} result = isatty(fileHandle(f)) != 0'i32 - -proc styledEchoProcessArg(s: string) = write stdout, s -proc styledEchoProcessArg(style: TStyle) = setStyle({style}) -proc styledEchoProcessArg(style: set[TStyle]) = setStyle style -proc styledEchoProcessArg(color: TForegroundColor) = setForeGroundColor color -proc styledEchoProcessArg(color: TBackgroundColor) = setBackGroundColor color - + +proc styledEchoProcessArg(s: string) = write stdout, s +proc styledEchoProcessArg(style: TStyle) = setStyle({style}) +proc styledEchoProcessArg(style: set[TStyle]) = setStyle style +proc styledEchoProcessArg(color: TForegroundColor) = setForeGroundColor color +proc styledEchoProcessArg(color: TBackgroundColor) = setBackGroundColor color + macro styledEcho*(m: varargs[expr]): stmt = ## to be documented. let m = callsite() - result = newNimNode(nnkStmtList) - - for i in countup(1, m.len - 1): - result.add(newCall(bindSym"styledEchoProcessArg", m[i])) - - result.add(newCall(bindSym"write", bindSym"stdout", newStrLitNode("\n"))) - result.add(newCall(bindSym"resetAttributes")) - -when isMainModule: - system.addQuitProc(resetAttributes) - write(stdout, "never mind") - eraseLine() - #setCursorPos(2, 2) - writeStyled("styled text ", {styleBright, styleBlink, styleUnderscore}) - setBackGroundColor(bgCyan, true) - setForeGroundColor(fgBlue) - writeln(stdout, "ordinary text") - + result = newNimNode(nnkStmtList) + + for i in countup(1, m.len - 1): + result.add(newCall(bindSym"styledEchoProcessArg", m[i])) + + result.add(newCall(bindSym"write", bindSym"stdout", newStrLitNode("\n"))) + result.add(newCall(bindSym"resetAttributes")) + +when isMainModule: + system.addQuitProc(resetAttributes) + write(stdout, "never mind") + eraseLine() + #setCursorPos(2, 2) + writeStyled("styled text ", {styleBright, styleBlink, styleUnderscore}) + setBackGroundColor(bgCyan, true) + setForeGroundColor(fgBlue) + writeln(stdout, "ordinary text") + styledEcho("styled text ", {styleBright, styleBlink, styleUnderscore}) \ No newline at end of file diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index fa4925ee6..d448d2b10 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -489,7 +489,7 @@ proc FD_SET*(Socket: TWinSocket, FDSet: var TFDSet) = proc FD_ZERO*(FDSet: var TFDSet) = FDSet.fd_count = 0 -proc WSAStartup*(wVersionRequired: int16, WSData: var TWSAData): cint {. +proc WSAStartup*(wVersionRequired: int16, WSData: ptr TWSAData): cint {. stdcall, importc: "WSAStartup", dynlib: ws2dll.} proc getaddrinfo*(nodename, servname: cstring, hints: ptr TAddrInfo, diff --git a/lib/wrappers/gtk/glib2.nim b/lib/wrappers/gtk/glib2.nim index 4151ba954..3fa672c85 100644 --- a/lib/wrappers/gtk/glib2.nim +++ b/lib/wrappers/gtk/glib2.nim @@ -27,7 +27,7 @@ type gshort* = cshort glong* = clong gint* = cint - gboolean* = bool + gboolean* = distinct gint guchar* = char gushort* = int16 gulong* = int @@ -173,6 +173,12 @@ type TGBoxedFreeFunc* = proc (boxed: gpointer){.cdecl.} PGsource = pointer # I don't know and don't care +converter gbool*(nimbool: bool): gboolean = + return ord(nimbool).gboolean + +converter toBool*(gbool: gboolean): bool = + return int(gbool) == 1 + const G_TYPE_FUNDAMENTAL_SHIFT* = 2 G_TYPE_FUNDAMENTAL_MAX* = 255 shl G_TYPE_FUNDAMENTAL_SHIFT diff --git a/lib/wrappers/gtk/gtk2.nim b/lib/wrappers/gtk/gtk2.nim index 6b418024e..63e69130a 100644 --- a/lib/wrappers/gtk/gtk2.nim +++ b/lib/wrappers/gtk/gtk2.nim @@ -2,6 +2,8 @@ import glib2, atk, pango, gdk2pixbuf, gdk2 +export gbool, toBool + when defined(win32): const lib = "libgtk-win32-2.0-0.dll" diff --git a/tools/nimgrep.nim b/tools/nimgrep.nim index fa14f9e8e..c7893fe78 100644 --- a/tools/nimgrep.nim +++ b/tools/nimgrep.nim @@ -32,6 +32,7 @@ Options: --ignoreCase, -i be case insensitive --ignoreStyle, -y be style insensitive --ext:EX1|EX2|... only search the files with the given extension(s) + --nocolor output will be given without any colours. --verbose be verbose: list every processed file --help, -h shows this help --version, -v shows the version @@ -291,6 +292,7 @@ for kind, key, val in getopt(): of "ignorecase", "i": incl(options, optIgnoreCase) of "ignorestyle", "y": incl(options, optIgnoreStyle) of "ext": extensions = val.split('|') + of "nocolor": useWriteStyled = false of "verbose": incl(options, optVerbose) of "help", "h": writeHelp() of "version", "v": writeVersion() |