diff options
Diffstat (limited to 'lib/pure')
-rw-r--r-- | lib/pure/asynchttpserver.nim | 11 | ||||
-rw-r--r-- | lib/pure/collections/sequtils.nim | 4 | ||||
-rw-r--r-- | lib/pure/collections/tables.nim | 41 | ||||
-rw-r--r-- | lib/pure/future.nim | 21 | ||||
-rw-r--r-- | lib/pure/httpclient.nim | 6 | ||||
-rw-r--r-- | lib/pure/ioselects/ioselectors_kqueue.nim | 10 | ||||
-rw-r--r-- | lib/pure/os.nim | 161 | ||||
-rw-r--r-- | lib/pure/ospaths.nim | 4 | ||||
-rw-r--r-- | lib/pure/times.nim | 18 | ||||
-rw-r--r-- | lib/pure/unittest.nim | 24 |
10 files changed, 240 insertions, 60 deletions
diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index a658097f9..4c7037a4e 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -81,6 +81,17 @@ proc respond*(req: Request, code: HttpCode, content: string, ## content. ## ## This procedure will **not** close the client socket. + ## + ## Examples + ## -------- + ## .. code-block::nim + ## proc handler(req: Request) {.async.} = + ## if req.url.path == "/hello-world": + ## let msg = %* {"message": "Hello World"} + ## let headers = newHttpHeaders([("Content-Type","application/json")]) + ## await req.respond(Http200, $msg, headers) + ## else: + ## await req.respond(Http404, "Not Found") var msg = "HTTP/1.1 " & $code & "\c\L" if headers != nil: diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index f458b7636..45a148fbf 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -228,7 +228,7 @@ proc apply*[T](data: var seq[T], op: proc (x: var T) {.closure.}) ## var a = @["1", "2", "3", "4"] ## echo repr(a) ## # --> ["1", "2", "3", "4"] - ## map(a, proc(x: var string) = x &= "42") + ## apply(a, proc(x: var string) = x &= "42") ## echo repr(a) ## # --> ["142", "242", "342", "442"] ## @@ -247,7 +247,7 @@ proc apply*[T](data: var seq[T], op: proc (x: T): T {.closure.}) ## var a = @["1", "2", "3", "4"] ## echo repr(a) ## # --> ["1", "2", "3", "4"] - ## map(a, proc(x: string): string = x & "42") + ## apply(a, proc(x: string): string = x & "42") ## echo repr(a) ## # --> ["142", "242", "342", "442"] ## diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 778ea5ca3..fe75f9a58 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -338,7 +338,7 @@ proc hasKey*[A, B](t: TableRef[A, B], key: A): bool = ## returns true iff `key` is in the table `t`. result = t[].hasKey(key) -template equalsImpl(t) = +template equalsImpl(s, t: typed): typed = if s.counter == t.counter: # different insertion orders mean different 'data' seqs, so we have # to use the slow route here: @@ -348,7 +348,9 @@ template equalsImpl(t) = return true proc `==`*[A, B](s, t: Table[A, B]): bool = - equalsImpl(t) + ## The `==` operator for hash tables. Returns ``true`` iff the content of both + ## tables contains the same key-value pairs. Insert order does not matter. + equalsImpl(s, t) proc indexBy*[A, B, C](collection: A, index: proc(x: B): C): Table[C, B] = ## Index the collection with the proc provided. @@ -436,9 +438,12 @@ proc `$`*[A, B](t: TableRef[A, B]): string = dollarImpl() proc `==`*[A, B](s, t: TableRef[A, B]): bool = + ## The `==` operator for hash tables. Returns ``true`` iff either both tables + ## are ``nil`` or none is ``nil`` and the content of both tables contains the + ## same key-value pairs. Insert order does not matter. if isNil(s): result = isNil(t) elif isNil(t): result = false - else: equalsImpl(t[]) + else: equalsImpl(s[], t[]) proc newTableFrom*[A, B, C](collection: A, index: proc(x: B): C): TableRef[C, B] = ## Index the collection with the proc provided. @@ -464,12 +469,16 @@ proc len*[A, B](t: OrderedTable[A, B]): int {.inline.} = ## returns the number of keys in `t`. result = t.counter -proc clear*[A, B](t: var OrderedTable[A, B] | OrderedTableRef[A, B]) = +proc clear*[A, B](t: var OrderedTable[A, B]) = ## Resets the table so that it is empty. clearImpl() t.first = -1 t.last = -1 +proc clear*[A, B](t: var OrderedTableRef[A, B]) = + ## Resets the table so that is is empty. + clear(t[]) + template forAllOrderedPairs(yieldStmt: untyped) {.oldimmediate, dirty.} = var h = t.first while h >= 0: @@ -606,6 +615,15 @@ proc `$`*[A, B](t: OrderedTable[A, B]): string = ## The `$` operator for ordered hash tables. dollarImpl() +proc `==`*[A, B](s, t: OrderedTable[A, B]): bool = + ## The `==` operator for ordered hash tables. Both the content and the order + ## must be equal for this to return ``true``. + if s.counter == t.counter: + forAllOrderedPairs: + if s.data[h] != t.data[h]: return false + result = true + else: result = false + proc sort*[A, B](t: var OrderedTable[A, B], cmp: proc (x,y: (A, B)): int) = ## sorts `t` according to `cmp`. This modifies the internal list @@ -749,6 +767,11 @@ proc `$`*[A, B](t: OrderedTableRef[A, B]): string = ## The `$` operator for ordered hash tables. dollarImpl() +proc `==`*[A, B](s, t: OrderedTableRef[A, B]): bool = + ## The `==` operator for ordered hash tables. Both the content and the order + ## must be equal for this to return ``true``. + result = s[] == t[] + proc sort*[A, B](t: OrderedTableRef[A, B], cmp: proc (x,y: (A, B)): int) = ## sorts `t` according to `cmp`. This modifies the internal list @@ -916,6 +939,11 @@ proc `$`*[A](t: CountTable[A]): string = ## The `$` operator for count tables. dollarImpl() +proc `==`*[A](s, t: CountTable[A]): bool = + ## The `==` operator for count tables. Returns ``true`` iff both tables + ## contain the same keys with the same count. Insert order does not matter. + equalsImpl(s, t) + proc inc*[A](t: var CountTable[A], key: A, val = 1) = ## increments `t[key]` by `val`. var index = rawGet(t, key) @@ -1040,6 +1068,11 @@ proc `$`*[A](t: CountTableRef[A]): string = ## The `$` operator for count tables. dollarImpl() +proc `==`*[A](s, t: CountTableRef[A]): bool = + ## The `==` operator for count tables. Returns ``true`` iff both tables + ## contain the same keys with the same count. Insert order does not matter. + result = s[] == t[] + proc inc*[A](t: CountTableRef[A], key: A, val = 1) = ## increments `t[key]` by `val`. t[].inc(key, val) diff --git a/lib/pure/future.nim b/lib/pure/future.nim index 67975cfcb..2a6d29933 100644 --- a/lib/pure/future.nim +++ b/lib/pure/future.nim @@ -177,3 +177,24 @@ macro `[]`*(lc: ListComprehension, comp, typ: untyped): untyped = newIdentNode("@"), newNimNode(nnkBracket))), result)))) + + +macro dump*(x: typed): untyped = + ## Dumps the content of an expression, useful for debugging. + ## It accepts any expression and prints a textual representation + ## of the tree representing the expression - as it would appear in + ## source code - together with the value of the expression. + ## + ## As an example, + ## + ## .. code-block:: nim + ## let + ## x = 10 + ## y = 20 + ## dump(x + y) + ## + ## will print ``x + y = 30``. + let s = x.toStrLit + let r = quote do: + debugEcho `s`, " = ", `x` + return r \ No newline at end of file diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 5fa0d0bcc..c56d13b57 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -958,8 +958,10 @@ proc parseResponse(client: HttpClient | AsyncHttpClient, proc newConnection(client: HttpClient | AsyncHttpClient, url: Uri) {.multisync.} = if client.currentURL.hostname != url.hostname or - client.currentURL.scheme != url.scheme: - if client.connected: client.close() + client.currentURL.scheme != url.scheme or + client.currentURL.port != url.port: + if client.connected: + client.close() when client is HttpClient: client.socket = newSocket() diff --git a/lib/pure/ioselects/ioselectors_kqueue.nim b/lib/pure/ioselects/ioselectors_kqueue.nim index cdaeeae26..3c0cf4e90 100644 --- a/lib/pure/ioselects/ioselectors_kqueue.nim +++ b/lib/pure/ioselects/ioselectors_kqueue.nim @@ -26,8 +26,8 @@ when defined(macosx) or defined(freebsd): const MAX_DESCRIPTORS_ID = 29 # KERN_MAXFILESPERPROC (MacOS) else: const MAX_DESCRIPTORS_ID = 27 # KERN_MAXFILESPERPROC (FreeBSD) - proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr int, - newp: pointer, newplen: int): cint + proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize, + newp: pointer, newplen: csize): cint {.importc: "sysctl",header: """#include <sys/types.h> #include <sys/sysctl.h>"""} elif defined(netbsd) or defined(openbsd): @@ -35,8 +35,8 @@ elif defined(netbsd) or defined(openbsd): # KERN_MAXFILES, because KERN_MAXFILES is always bigger, # than KERN_MAXFILESPERPROC. const MAX_DESCRIPTORS_ID = 7 # KERN_MAXFILES - proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr int, - newp: pointer, newplen: int): cint + proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize, + newp: pointer, newplen: csize): cint {.importc: "sysctl",header: """#include <sys/param.h> #include <sys/sysctl.h>"""} @@ -72,7 +72,7 @@ type SelectEvent* = ptr SelectEventImpl proc newSelector*[T](): Selector[T] = var maxFD = 0.cint - var size = sizeof(cint) + var size = csize(sizeof(cint)) var namearr = [1.cint, MAX_DESCRIPTORS_ID.cint] # Obtain maximum number of file descriptors for process if sysctl(addr(namearr[0]), 2, cast[pointer](addr maxFD), addr size, diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 110831d95..8f8f36b80 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -50,6 +50,8 @@ proc c_getenv(env: cstring): cstring {. importc: "getenv", header: "<stdlib.h>".} proc c_putenv(env: cstring): cint {. importc: "putenv", header: "<stdlib.h>".} +proc c_free(p: pointer) {. + importc: "free", header: "<stdlib.h>".} var errno {.importc, header: "<errno.h>".}: cint @@ -303,24 +305,47 @@ proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1".} = proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} = ## Returns the `current working directory`:idx:. - const bufsize = 512 # should be enough when defined(windows): + var bufsize = MAX_PATH.int32 when useWinUnicode: var res = newWideCString("", bufsize) - var L = getCurrentDirectoryW(bufsize, res) - if L == 0'i32: raiseOSError(osLastError()) - result = res$L + while true: + var L = getCurrentDirectoryW(bufsize, res) + if L == 0'i32: + raiseOSError(osLastError()) + elif L > bufsize: + res = newWideCString("", L) + bufsize = L + else: + result = res$L + break else: result = newString(bufsize) - var L = getCurrentDirectoryA(bufsize, result) - if L == 0'i32: raiseOSError(osLastError()) - setLen(result, L) + while true: + var L = getCurrentDirectoryA(bufsize, result) + if L == 0'i32: + raiseOSError(osLastError()) + elif L > bufsize: + result = newString(L) + bufsize = L + else: + setLen(result, L) + break else: + var bufsize = 1024 # should be enough result = newString(bufsize) - if getcwd(result, bufsize) != nil: - setLen(result, c_strlen(result)) - else: - raiseOSError(osLastError()) + while true: + if getcwd(result, bufsize) != nil: + setLen(result, c_strlen(result)) + break + else: + let err = osLastError() + if err.int32 == ERANGE: + bufsize = bufsize shl 1 + doAssert(bufsize >= 0) + result = newString(bufsize) + else: + raiseOSError(osLastError()) proc setCurrentDir*(newDir: string) {.inline, tags: [].} = ## Sets the `current working directory`:idx:; `OSError` is raised if @@ -336,28 +361,45 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [].} = proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", tags: [ReadDirEffect].} = - ## Returns the full (`absolute`:idx:) path of the file `filename`, raises OSError in case of an error. + ## Returns the full (`absolute`:idx:) path of the file `filename`, + ## raises OSError in case of an error. when defined(windows): - const bufsize = 3072'i32 + var bufsize = MAX_PATH.int32 when useWinUnicode: - var unused: WideCString - var res = newWideCString("", bufsize div 2) - var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused) - if L <= 0'i32 or L >= bufsize: - raiseOSError(osLastError()) - result = res$L + var unused: WideCString = nil + var res = newWideCString("", bufsize) + while true: + var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused) + if L == 0'i32: + raiseOSError(osLastError()) + elif L > bufsize: + res = newWideCString("", L) + bufsize = L + else: + result = res$L + break else: - var unused: cstring + var unused: cstring = nil result = newString(bufsize) - var L = getFullPathNameA(filename, bufsize, result, unused) - if L <= 0'i32 or L >= bufsize: raiseOSError(osLastError()) - setLen(result, L) + while true: + var L = getFullPathNameA(filename, bufsize, result, unused) + if L == 0'i32: + raiseOSError(osLastError()) + elif L > bufsize: + result = newString(L) + bufsize = L + else: + setLen(result, L) + break else: - # careful, realpath needs to take an allocated buffer according to Posix: - result = newString(pathMax) - var r = realpath(filename, result) - if r.isNil: raiseOSError(osLastError()) - setLen(result, c_strlen(result)) + # according to Posix we don't need to allocate space for result pathname. + # But we need to free return value with free(3). + var r = realpath(filename, nil) + if r.isNil: + raiseOSError(osLastError()) + else: + result = $r + c_free(cast[pointer](r)) when defined(Windows): proc openHandle(path: string, followSymlink=true): Handle = @@ -1420,6 +1462,35 @@ when declared(paramCount) or defined(nimdoc): for i in 1..paramCount(): result.add(paramStr(i)) +when defined(freebsd): + proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize, + newp: pointer, newplen: csize): cint + {.importc: "sysctl",header: """#include <sys/types.h> + #include <sys/sysctl.h>"""} + const + CTL_KERN = 1 + KERN_PROC = 14 + KERN_PROC_PATHNAME = 12 + MAX_PATH = 1024 + + proc getApplFreebsd(): string = + var pathLength = csize(MAX_PATH) + result = newString(pathLength) + var req = [CTL_KERN.cint, KERN_PROC.cint, KERN_PROC_PATHNAME.cint, -1.cint] + while true: + let res = sysctl(addr req[0], 4, cast[pointer](addr result[0]), + addr pathLength, nil, 0) + if res < 0: + let err = osLastError() + if err.int32 == ENOMEM: + result = newString(pathLength) + else: + result.setLen(0) # error! + break + else: + result.setLen(pathLength) + break + when defined(linux) or defined(solaris) or defined(bsd) or defined(aix): proc getApplAux(procPath: string): string = result = newString(256) @@ -1464,16 +1535,34 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = # Solaris: # /proc/<pid>/object/a.out (filename only) # /proc/<pid>/path/a.out (complete pathname) - # FreeBSD: /proc/<pid>/file when defined(windows): + var bufsize = int32(MAX_PATH) when useWinUnicode: - var buf = newWideCString("", 256) - var len = getModuleFileNameW(0, buf, 256) - result = buf$len + var buf = newWideCString("", bufsize) + while true: + var L = getModuleFileNameW(0, buf, bufsize) + if L == 0'i32: + result = "" # error! + break + elif L > bufsize: + buf = newWideCString("", L) + bufsize = L + else: + result = buf$L + break else: - result = newString(256) - var len = getModuleFileNameA(0, result, 256) - setlen(result, int(len)) + result = newString(bufsize) + while true: + var L = getModuleFileNameA(0, result, bufsize) + if L == 0'i32: + result = "" # error! + break + elif L > bufsize: + result = newString(L) + bufsize = L + else: + setLen(result, L) + break elif defined(macosx): var size: cuint32 getExecPath1(nil, size) @@ -1488,7 +1577,7 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = elif defined(solaris): result = getApplAux("/proc/" & $getpid() & "/path/a.out") elif defined(freebsd): - result = getApplAux("/proc/" & $getpid() & "/file") + result = getApplFreebsd() # little heuristic that may work on other POSIX-like systems: if result.len == 0: result = getApplHeuristic() diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim index 56671ee62..3d3a105f0 100644 --- a/lib/pure/ospaths.nim +++ b/lib/pure/ospaths.nim @@ -434,8 +434,8 @@ when not declared(getEnv) or defined(nimscript): ## On Windows, network paths are considered absolute too. when doslike: var len = len(path) - result = (len > 1 and path[0] in {'/', '\\'}) or - (len > 2 and path[0] in {'a'..'z', 'A'..'Z'} and path[1] == ':') + result = (len > 0 and path[0] in {'/', '\\'}) or + (len > 1 and path[0] in {'a'..'z', 'A'..'Z'} and path[1] == ':') elif defined(macos): result = path.len > 0 and path[0] != ':' elif defined(RISCOS): diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 3e3176ab8..efc1dfa92 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1248,12 +1248,18 @@ proc parse*(value, layout: string): TimeInfo = else: parseToken(info, token, value, j) token = "" - # Reset weekday (might not have been provided and the default may be wrong) - # and yearday (is never provided directly and therefore probably wrong) - let processed = getLocalTime(toTime(info)) - info.weekday = processed.weekday - info.yearday = processed.yearday - return info + + # We are going to process the date to find out if we are in DST, because the + # default based on the current time may be wrong. Calling getLocalTime will + # set this correctly, but the actual time may be offset from when we called + # toTime with a possibly incorrect DST setting, so we are only going to take + # the isDST from this result. + let correctDST = getLocalTime(toTime(info)) + info.isDST = correctDST.isDST + + # Now we preocess it again with the correct isDST to correct things like + # weekday and yearday. + return getLocalTime(toTime(info)) # Leap year calculations are adapted from: # http://www.codeproject.com/Articles/7358/Ultra-fast-Algorithms-for-Working-with-Leap-Years diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index 0fc2e441e..12553e3da 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -11,11 +11,21 @@ ## ## This module implements boilerplate to make unit testing easy. ## +## The test status and name is printed after any output or traceback. +## ## Example: ## ## .. code:: nim ## ## suite "description for this stuff": +## echo "suite setup: run once before the tests" +## +## setup: +## echo "run before each test" +## +## teardown: +## echo "run after each test": +## ## test "essential truths": ## # give up and stop if this fails ## require(true) @@ -30,6 +40,13 @@ ## let v = @[1, 2, 3] # you can do initialization here ## expect(IndexError): ## discard v[4] +## +## echo "suite teardown: run once after the tests" +## +## +## Tests can be nested, however failure of a nested test will not mark the +## parent test as failed. Setup and teardown are inherited. Setup can be +## overridden locally. import macros @@ -226,10 +243,11 @@ template fail* = checkpoints = @[] template skip* = - ## Makes test to be skipped. Should be used directly + ## Mark the test as skipped. Should be used directly ## in case when it is not possible to perform test ## for reasons depending on outer environment, ## or certain application logic conditions or configurations. + ## The test code is still executed. ## ## .. code-block:: nim ## @@ -250,12 +268,12 @@ macro check*(conditions: untyped): untyped = ## ## import strutils ## - ## check("AKB48".toLower() == "akb48") + ## check("AKB48".toLowerAscii() == "akb48") ## ## let teams = {'A', 'K', 'B', '4', '8'} ## ## check: - ## "AKB48".toLower() == "akb48" + ## "AKB48".toLowerAscii() == "akb48" ## 'C' in teams let checked = callsite()[1] var |