diff options
Diffstat (limited to 'lib/pure')
-rw-r--r-- | lib/pure/asynchttpserver.nim | 15 | ||||
-rw-r--r-- | lib/pure/cgi.nim | 2 | ||||
-rw-r--r-- | lib/pure/collections/critbits.nim | 32 | ||||
-rw-r--r-- | lib/pure/collections/intsets.nim | 2 | ||||
-rw-r--r-- | lib/pure/collections/sequtils.nim | 296 | ||||
-rw-r--r-- | lib/pure/collections/sets.nim | 11 | ||||
-rw-r--r-- | lib/pure/collections/tables.nim | 155 | ||||
-rw-r--r-- | lib/pure/hashes.nim | 74 | ||||
-rw-r--r-- | lib/pure/logging.nim | 27 | ||||
-rw-r--r-- | lib/pure/marshal.nim | 2 | ||||
-rw-r--r-- | lib/pure/md5.nim | 2 | ||||
-rw-r--r-- | lib/pure/mimetypes.nim | 2 | ||||
-rw-r--r-- | lib/pure/ospaths.nim | 46 | ||||
-rw-r--r-- | lib/pure/parseopt.nim | 178 | ||||
-rw-r--r-- | lib/pure/scgi.nim | 11 | ||||
-rw-r--r-- | lib/pure/strtabs.nim | 32 | ||||
-rw-r--r-- | lib/pure/strutils.nim | 178 | ||||
-rwxr-xr-x | lib/pure/unittest.nim | 3 | ||||
-rw-r--r-- | lib/pure/xmltree.nim | 11 |
19 files changed, 914 insertions, 165 deletions
diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index 5d74896bf..590b52c1a 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -212,7 +212,7 @@ proc processClient(client: AsyncSocket, address: string, if request.reqMethod == "post": # Check for Expect header if request.headers.hasKey("Expect"): - if request.headers["Expect"].toLower == "100-continue": + if request.headers.getOrDefault("Expect").toLower == "100-continue": await client.sendStatus("100 Continue") else: await client.sendStatus("417 Expectation Failed") @@ -221,7 +221,8 @@ proc processClient(client: AsyncSocket, address: string, # - Check for Content-length header if request.headers.hasKey("Content-Length"): var contentLength = 0 - if parseInt(request.headers["Content-Length"], contentLength) == 0: + if parseInt(request.headers.getOrDefault("Content-Length"), + contentLength) == 0: await request.respond(Http400, "Bad Request. Invalid Content-Length.") continue else: @@ -232,16 +233,18 @@ proc processClient(client: AsyncSocket, address: string, continue case request.reqMethod - of "get", "post", "head", "put", "delete", "trace", "options", "connect", "patch": + of "get", "post", "head", "put", "delete", "trace", "options", + "connect", "patch": await callback(request) else: - await request.respond(Http400, "Invalid request method. Got: " & request.reqMethod) + await request.respond(Http400, "Invalid request method. Got: " & + request.reqMethod) # Persistent connections if (request.protocol == HttpVer11 and - request.headers["connection"].normalize != "close") or + request.headers.getOrDefault("connection").normalize != "close") or (request.protocol == HttpVer10 and - request.headers["connection"].normalize == "keep-alive"): + request.headers.getOrDefault("connection").normalize == "keep-alive"): # In HTTP 1.1 we assume that connection is persistent. Unless connection # header states otherwise. # In HTTP 1.0 we assume that the connection should not be persistent. diff --git a/lib/pure/cgi.nim b/lib/pure/cgi.nim index cfd768f91..200a4adf1 100644 --- a/lib/pure/cgi.nim +++ b/lib/pure/cgi.nim @@ -387,7 +387,7 @@ var proc getCookie*(name: string): TaintedString = ## Gets a cookie. If no cookie of `name` exists, "" is returned. if gcookies == nil: gcookies = parseCookies(getHttpCookie()) - result = TaintedString(gcookies[name]) + result = TaintedString(gcookies.getOrDefault(name)) proc existsCookie*(name: string): bool = ## Checks if a cookie of `name` exists. diff --git a/lib/pure/collections/critbits.nim b/lib/pure/collections/critbits.nim index 09b20fd45..8c507d4fb 100644 --- a/lib/pure/collections/critbits.nim +++ b/lib/pure/collections/critbits.nim @@ -11,6 +11,8 @@ ## container for a set or a mapping of strings. Based on the excellent paper ## by Adam Langley. +include "system/inclrtl" + type NodeObj[T] = object {.acyclic.} byte: int ## byte index of the difference @@ -140,20 +142,32 @@ proc `[]=`*[T](c: var CritBitTree[T], key: string, val: T) = var n = rawInsert(c, key) n.val = val -proc `[]`*[T](c: CritBitTree[T], key: string): T {.inline.} = - ## retrieves the value at ``c[key]``. If `key` is not in `t`, - ## default empty value for the type `B` is returned - ## and no exception is raised. One can check with ``hasKey`` whether the key - ## exists. +template get[T](c: CritBitTree[T], key: string): T {.immediate.} = let n = rawGet(c, key) if n != nil: result = n.val + else: + when compiles($key): + raise newException(KeyError, "key not found: " & $key) + else: + raise newException(KeyError, "key not found") -proc mget*[T](c: var CritBitTree[T], key: string): var T {.inline.} = +proc `[]`*[T](c: CritBitTree[T], key: string): T {.inline, deprecatedGet.} = + ## retrieves the value at ``c[key]``. If `key` is not in `t`, the + ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether + ## the key exists. + get(c, key) + +proc `[]`*[T](c: var CritBitTree[T], key: string): var T {.inline, + deprecatedGet.} = ## retrieves the value at ``c[key]``. The value can be modified. ## If `key` is not in `t`, the ``KeyError`` exception is raised. - let n = rawGet(c, key) - if n != nil: result = n.val - else: raise newException(KeyError, "key not found: " & $key) + get(c, key) + +proc mget*[T](c: var CritBitTree[T], key: string): var T {.inline, deprecated.} = + ## retrieves the value at ``c[key]``. The value can be modified. + ## If `key` is not in `t`, the ``KeyError`` exception is raised. + ## Use ```[]``` instead. + get(c, key) proc excl*[T](c: var CritBitTree[T], key: string) = ## removes `key` (and its associated value) from the set `c`. diff --git a/lib/pure/collections/intsets.nim b/lib/pure/collections/intsets.nim index 38bc9d462..603a4b595 100644 --- a/lib/pure/collections/intsets.nim +++ b/lib/pure/collections/intsets.nim @@ -138,6 +138,8 @@ proc initIntSet*: IntSet = result.counter = 0 result.head = nil +proc isNil*(x: IntSet): bool {.inline.} = x.head.isNil + proc assign*(dest: var IntSet, src: IntSet) = ## copies `src` to `dest`. `dest` does not need to be initialized by ## `initIntSet`. diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index e6ea19a6b..71babe93b 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -47,7 +47,7 @@ proc concat*[T](seqs: varargs[seq[T]]): seq[T] = result[i] = itm inc(i) -proc repeat*[T](s: seq[T], n: Natural): seq[T] = +proc cycle*[T](s: seq[T], n: Natural): seq[T] = ## Returns a new sequence with the items of `s` repeated `n` times. ## ## Example: @@ -56,15 +56,29 @@ proc repeat*[T](s: seq[T], n: Natural): seq[T] = ## ## let ## s = @[1, 2, 3] - ## total = s.repeat(3) + ## total = s.cycle(3) ## assert total == @[1, 2, 3, 1, 2, 3, 1, 2, 3] result = newSeq[T](n * s.len) var o = 0 - for x in 1..n: + for x in 0..<n: for e in s: result[o] = e inc o +proc repeat*[T](x: T, n: Natural): seq[T] = + ## Returns a new sequence with the item `x` repeated `n` times. + ## + ## Example: + ## + ## .. code-block: + ## + ## let + ## total = repeat(5, 3) + ## assert total == @[5, 5, 5] + result = newSeq[T](n) + for i in 0..<n: + result[i] = x + proc deduplicate*[T](seq1: seq[T]): seq[T] = ## Returns a new sequence without duplicates. ## @@ -169,6 +183,77 @@ proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = first = last +proc map*[T, S](data: openArray[T], op: proc (x: T): S {.closure.}): + seq[S]{.inline.} = + ## Returns a new sequence with the results of `op` applied to every item in + ## `data`. + ## + ## Since the input is not modified you can use this version of ``map`` to + ## transform the type of the elements in the input sequence. Example: + ## + ## .. code-block:: nim + ## let + ## a = @[1, 2, 3, 4] + ## b = map(a, proc(x: int): string = $x) + ## assert b == @["1", "2", "3", "4"] + newSeq(result, data.len) + for i in 0..data.len-1: result[i] = op(data[i]) + +proc map*[T](data: var openArray[T], op: proc (x: var T) {.closure.}) + {.deprecated.} = + ## Applies `op` to every item in `data` modifying it directly. + ## + ## Note that this version of ``map`` requires your input and output types to + ## be the same, since they are modified in-place. Example: + ## + ## .. code-block:: nim + ## var a = @["1", "2", "3", "4"] + ## echo repr(a) + ## # --> ["1", "2", "3", "4"] + ## map(a, proc(x: var string) = x &= "42") + ## echo repr(a) + ## # --> ["142", "242", "342", "442"] + ## **Deprecated since version 0.12.0:** Use the ``apply`` proc instead. + for i in 0..data.len-1: op(data[i]) + +proc apply*[T](data: var seq[T], op: proc (x: var T) {.closure.}) + {.inline.} = + ## Applies `op` to every item in `data` modifying it directly. + ## + ## Note that this requires your input and output types to + ## be the same, since they are modified in-place. + ## The parameter function takes a ``var T`` type parameter. + ## Example: + ## + ## .. code-block:: nim + ## var a = @["1", "2", "3", "4"] + ## echo repr(a) + ## # --> ["1", "2", "3", "4"] + ## map(a, proc(x: var string) = x &= "42") + ## echo repr(a) + ## # --> ["142", "242", "342", "442"] + ## + for i in 0..data.len-1: op(data[i]) + +proc apply*[T](data: var seq[T], op: proc (x: T): T {.closure.}) + {.inline.} = + ## Applies `op` to every item in `data` modifying it directly. + ## + ## Note that this requires your input and output types to + ## be the same, since they are modified in-place. + ## The parameter function takes and returns a ``T`` type variable. + ## Example: + ## + ## .. code-block:: nim + ## var a = @["1", "2", "3", "4"] + ## echo repr(a) + ## # --> ["1", "2", "3", "4"] + ## map(a, proc(x: string): string = x & "42") + ## echo repr(a) + ## # --> ["142", "242", "342", "442"] + ## + for i in 0..data.len-1: data[i] = op(data[i]) + iterator filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): T = ## Iterates through a sequence and yields every item that fulfills the @@ -181,11 +266,12 @@ iterator filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): T = ## for n in filter(numbers, proc (x: int): bool = x mod 2 == 0): ## echo($n) ## # echoes 4, 8, 4 in separate lines - for i in countup(0, len(seq1)-1): - var item = seq1[i] - if pred(item): yield seq1[i] + for i in 0..<seq1.len: + if pred(seq1[i]): + yield seq1[i] -proc filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): seq[T] = +proc filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): seq[T] + {.inline.} = ## Returns a new sequence with all the items that fulfilled the predicate. ## ## Example: @@ -197,9 +283,13 @@ proc filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): seq[T] = ## f2 = filter(colors) do (x: string) -> bool : x.len > 5 ## assert f1 == @["red", "black"] ## assert f2 == @["yellow"] - accumulateResult(filter(seq1, pred)) + result = newSeq[T]() + for i in 0..<seq1.len: + if pred(seq1[i]): + result.add(seq1[i]) -proc keepIf*[T](seq1: var seq[T], pred: proc(item: T): bool {.closure.}) = +proc keepIf*[T](seq1: var seq[T], pred: proc(item: T): bool {.closure.}) + {.inline.} = ## Keeps the items in the passed sequence if they fulfilled the predicate. ## Same as the ``filter`` proc, but modifies the sequence directly. ## @@ -213,7 +303,7 @@ proc keepIf*[T](seq1: var seq[T], pred: proc(item: T): bool {.closure.}) = for i in 0 .. <len(seq1): if pred(seq1[i]): if pos != i: - seq1[pos] = seq1[i] + shallowCopy(seq1[pos], seq1[i]) inc(pos) setLen(seq1, pos) @@ -268,7 +358,7 @@ proc insert*[T](dest: var seq[T], src: openArray[T], pos=0) = inc(j) -template filterIt*(seq1, pred: expr): expr {.immediate.} = +template filterIt*(seq1, pred: expr): expr = ## Returns a new sequence with all the items that fulfilled the predicate. ## ## Unlike the `proc` version, the predicate needs to be an expression using @@ -282,12 +372,12 @@ template filterIt*(seq1, pred: expr): expr {.immediate.} = ## notAcceptable = filterIt(temperatures, it > 50 or it < -10) ## assert acceptable == @[-2.0, 24.5, 44.31] ## assert notAcceptable == @[-272.15, 99.9, -113.44] - var result {.gensym.}: type(seq1) = @[] + var result {.gensym.} = newSeq[type(seq1[0])]() for it {.inject.} in items(seq1): if pred: result.add(it) result -template keepItIf*(varSeq, pred: expr) = +template keepItIf*(varSeq: seq, pred: expr) = ## Convenience template around the ``keepIf`` proc to reduce typing. ## ## Unlike the `proc` version, the predicate needs to be an expression using @@ -303,10 +393,71 @@ template keepItIf*(varSeq, pred: expr) = let it {.inject.} = varSeq[i] if pred: if pos != i: - varSeq[pos] = varSeq[i] + shallowCopy(varSeq[pos], varSeq[i]) inc(pos) setLen(varSeq, pos) +proc all*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): bool = + ## Iterates through a sequence and checks if every item fulfills the + ## predicate. + ## + ## Example: + ## + ## .. code-block:: + ## let numbers = @[1, 4, 5, 8, 9, 7, 4] + ## assert all(numbers, proc (x: int): bool = return x < 10) == true + ## assert all(numbers, proc (x: int): bool = return x < 9) == false + for i in seq1: + if not pred(i): + return false + return true + +template allIt*(seq1, pred: expr): bool {.immediate.} = + ## Checks if every item fulfills the predicate. + ## + ## Example: + ## + ## .. code-block:: + ## let numbers = @[1, 4, 5, 8, 9, 7, 4] + ## assert allIt(numbers, it < 10) == true + ## assert allIt(numbers, it < 9) == false + var result {.gensym.} = true + for it {.inject.} in items(seq1): + if not pred: + result = false + break + result + +proc any*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): bool = + ## Iterates through a sequence and checks if some item fulfills the + ## predicate. + ## + ## Example: + ## + ## .. code-block:: + ## let numbers = @[1, 4, 5, 8, 9, 7, 4] + ## assert any(numbers, proc (x: int): bool = return x > 8) == true + ## assert any(numbers, proc (x: int): bool = return x > 9) == false + for i in seq1: + if pred(i): + return true + return false + +template anyIt*(seq1, pred: expr): bool {.immediate.} = + ## Checks if some item fulfills the predicate. + ## + ## Example: + ## + ## .. code-block:: + ## let numbers = @[1, 4, 5, 8, 9, 7, 4] + ## assert anyIt(numbers, it > 8) == true + ## assert anyIt(numbers, it > 9) == false + var result {.gensym.} = false + for it {.inject.} in items(seq1): + if pred: + result = true + break + result template toSeq*(iter: expr): expr {.immediate.} = ## Transforms any iterator into a sequence. @@ -320,14 +471,19 @@ template toSeq*(iter: expr): expr {.immediate.} = ## if x mod 2 == 1: ## result = true) ## assert odd_numbers == @[1, 3, 5, 7, 9] - ## - ## **Note**: Since this is an immediate macro, you cannot always invoke this - ## as ``x.toSeq``, depending on the ``x``. - ## See `this <manual.html#limitations-of-the-method-call-syntax>`_ - ## for an explanation. - var result {.gensym.}: seq[type(iter)] = @[] - for x in iter: add(result, x) - result + + when compiles(iter.len): + var i = 0 + var result = newSeq[type(iter)](iter.len) + for x in iter: + result[i] = x + inc i + result + else: + var result: seq[type(iter)] = @[] + for x in iter: + result.add(x) + result template foldl*(sequence, operation: expr): expr = ## Template to fold a sequence from left to right, returning the accumulation. @@ -358,7 +514,7 @@ template foldl*(sequence, operation: expr): expr = assert sequence.len > 0, "Can't fold empty sequences" var result {.gensym.}: type(sequence[0]) result = sequence[0] - for i in countup(1, sequence.len - 1): + for i in 1..<sequence.len: let a {.inject.} = result b {.inject.} = sequence[i] @@ -401,7 +557,7 @@ template foldr*(sequence, operation: expr): expr = result = operation result -template mapIt*(seq1, typ, op: expr): expr = +template mapIt*(seq1, typ, op: expr): expr {.deprecated.}= ## Convenience template around the ``map`` proc to reduce typing. ## ## The template injects the ``it`` variable which you can use directly in an @@ -414,13 +570,45 @@ template mapIt*(seq1, typ, op: expr): expr = ## nums = @[1, 2, 3, 4] ## strings = nums.mapIt(string, $(4 * it)) ## assert strings == @["4", "8", "12", "16"] + ## **Deprecated since version 0.12.0:** Use the ``mapIt(seq1, op)`` + ## template instead. var result {.gensym.}: seq[typ] = @[] for it {.inject.} in items(seq1): result.add(op) result -template mapIt*(varSeq, op: expr) = - ## Convenience template around the mutable ``map`` proc to reduce typing. + +template mapIt*(seq1, op: expr): expr = + ## Convenience template around the ``map`` proc to reduce typing. + ## + ## The template injects the ``it`` variable which you can use directly in an + ## expression. Example: + ## + ## .. code-block:: + ## let + ## nums = @[1, 2, 3, 4] + ## strings = nums.mapIt($(4 * it)) + ## assert strings == @["4", "8", "12", "16"] + type outType = type(( + block: + var it{.inject.}: type(items(seq1)); + op)) + var result: seq[outType] + when compiles(seq1.len): + let s = seq1 + var i = 0 + result = newSeq[outType](s.len) + for it {.inject.} in s: + result[i] = op + i += 1 + else: + result = @[] + for it {.inject.} in seq1: + result.add(op) + result + +template applyIt*(varSeq, op: expr) = + ## Convenience template around the mutable ``apply`` proc to reduce typing. ## ## The template injects the ``it`` variable which you can use directly in an ## expression. The expression has to return the same type as the sequence you @@ -428,12 +616,14 @@ template mapIt*(varSeq, op: expr) = ## ## .. code-block:: ## var nums = @[1, 2, 3, 4] - ## nums.mapIt(it * 3) + ## nums.applyIt(it * 3) ## assert nums[0] + nums[3] == 15 - for i in 0 .. <len(varSeq): + for i in 0 .. <varSeq.len: let it {.inject.} = varSeq[i] varSeq[i] = op + + template newSeqWith*(len: int, init: expr): expr = ## creates a new sequence, calling `init` to initialize each value. Example: ## @@ -513,6 +703,38 @@ when isMainModule: keepItIf(candidates, it.len == 3 and it[0] == 'b') assert candidates == @["bar", "baz"] + block: # any + let + numbers = @[1, 4, 5, 8, 9, 7, 4] + len0seq : seq[int] = @[] + assert any(numbers, proc (x: int): bool = return x > 8) == true + assert any(numbers, proc (x: int): bool = return x > 9) == false + assert any(len0seq, proc (x: int): bool = return true) == false + + block: # anyIt + let + numbers = @[1, 4, 5, 8, 9, 7, 4] + len0seq : seq[int] = @[] + assert anyIt(numbers, it > 8) == true + assert anyIt(numbers, it > 9) == false + assert anyIt(len0seq, true) == false + + block: # all + let + numbers = @[1, 4, 5, 8, 9, 7, 4] + len0seq : seq[int] = @[] + assert all(numbers, proc (x: int): bool = return x < 10) == true + assert all(numbers, proc (x: int): bool = return x < 9) == false + assert all(len0seq, proc (x: int): bool = return false) == true + + block: # allIt + let + numbers = @[1, 4, 5, 8, 9, 7, 4] + len0seq : seq[int] = @[] + assert allIt(numbers, it < 10) == true + assert allIt(numbers, it < 9) == false + assert allIt(len0seq, false) == true + block: # toSeq test let numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9] @@ -568,8 +790,8 @@ when isMainModule: block: # mapIt tests var nums = @[1, 2, 3, 4] - strings = nums.mapIt(string, $(4 * it)) - nums.mapIt(it * 3) + strings = nums.mapIt($(4 * it)) + nums.applyIt(it * 3) assert nums[0] + nums[3] == 15 block: # distribute tests @@ -605,15 +827,19 @@ when isMainModule: seq2D[0][1] = true doAssert seq2D == @[@[true, true], @[true, false], @[false, false], @[false, false]] - block: # repeat tests + block: # cycle tests let a = @[1, 2, 3] b: seq[int] = @[] - doAssert a.repeat(3) == @[1, 2, 3, 1, 2, 3, 1, 2, 3] - doAssert a.repeat(0) == @[] - #doAssert a.repeat(-1) == @[] # will not compile! - doAssert b.repeat(3) == @[] + doAssert a.cycle(3) == @[1, 2, 3, 1, 2, 3, 1, 2, 3] + doAssert a.cycle(0) == @[] + #doAssert a.cycle(-1) == @[] # will not compile! + doAssert b.cycle(3) == @[] + + block: # repeat tests + assert repeat(10, 5) == @[10, 10, 10, 10, 10] + assert repeat(@[1,2,3], 2) == @[@[1,2,3], @[1,2,3]] when not defined(testing): echo "Finished doc tests" diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index 3d4de8fdc..abe9cf85e 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -154,9 +154,9 @@ proc rawGetKnownHC[A](s: HashSet[A], key: A, hc: Hash): int {.inline.} = proc rawGet[A](s: HashSet[A], key: A, hc: var Hash): int {.inline.} = rawGetImpl() -proc mget*[A](s: var HashSet[A], key: A): var A = +proc `[]`*[A](s: var HashSet[A], key: A): var A = ## returns the element that is actually stored in 's' which has the same - ## value as 'key' or raises the ``EInvalidKey`` exception. This is useful + ## value as 'key' or raises the ``KeyError`` exception. This is useful ## when one overloaded 'hash' and '==' but still needs reference semantics ## for sharing. assert s.isValid, "The set needs to be initialized." @@ -165,6 +165,13 @@ proc mget*[A](s: var HashSet[A], key: A): var A = if index >= 0: result = s.data[index].key else: raise newException(KeyError, "key not found: " & $key) +proc mget*[A](s: var HashSet[A], key: A): var A {.deprecated.} = + ## returns the element that is actually stored in 's' which has the same + ## value as 'key' or raises the ``KeyError`` exception. This is useful + ## when one overloaded 'hash' and '==' but still needs reference semantics + ## for sharing. Use ```[]``` instead. + s[key] + proc contains*[A](s: HashSet[A], key: A): bool = ## Returns true iff `key` is in `s`. ## diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index be6b755ed..329b2a1cb 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -68,6 +68,8 @@ import hashes, math +include "system/inclrtl" + type KeyValuePair[A, B] = tuple[hcode: Hash, key: A, val: B] KeyValuePairSeq[A, B] = seq[KeyValuePair[A, B]] @@ -96,18 +98,10 @@ proc len*[A, B](t: Table[A, B]): int = ## returns the number of keys in `t`. result = t.counter -proc `[]`*[A, B](t: Table[A, B], key: A): B = - ## retrieves the value at ``t[key]``. If `key` is not in `t`, - ## default empty value for the type `B` is returned - ## and no exception is raised. One can check with ``hasKey`` whether the key - ## exists. - var hc: Hash - var index = rawGet(t, key, hc) - if index >= 0: result = t.data[index].val - -proc mget*[A, B](t: var Table[A, B], key: A): var B = +template get(t, key): untyped {.immediate.} = ## retrieves the value at ``t[key]``. The value can be modified. ## If `key` is not in `t`, the ``KeyError`` exception is raised. + mixin rawGet var hc: Hash var index = rawGet(t, key, hc) if index >= 0: result = t.data[index].val @@ -117,6 +111,31 @@ proc mget*[A, B](t: var Table[A, B], key: A): var B = else: raise newException(KeyError, "key not found") +template getOrDefaultImpl(t, key): untyped {.immediate.} = + mixin rawGet + var hc: Hash + var index = rawGet(t, key, hc) + if index >= 0: result = t.data[index].val + +proc `[]`*[A, B](t: Table[A, B], key: A): B {.deprecatedGet.} = + ## retrieves the value at ``t[key]``. If `key` is not in `t`, the + ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether + ## the key exists. + get(t, key) + +proc `[]`*[A, B](t: var Table[A, B], key: A): var B {.deprecatedGet.} = + ## retrieves the value at ``t[key]``. The value can be modified. + ## If `key` is not in `t`, the ``KeyError`` exception is raised. + get(t, key) + +proc mget*[A, B](t: var Table[A, B], key: A): var B {.deprecated.} = + ## retrieves the value at ``t[key]``. The value can be modified. + ## If `key` is not in `t`, the ``KeyError`` exception is raised. Use ```[]``` + ## instead. + get(t, key) + +proc getOrDefault*[A, B](t: Table[A, B], key: A): B = getOrDefaultImpl(t, key) + iterator allValues*[A, B](t: Table[A, B]; key: A): B = ## iterates over any value in the table `t` that belongs to the given `key`. var h: Hash = hash(key) and high(t.data) @@ -276,17 +295,19 @@ iterator mvalues*[A, B](t: TableRef[A, B]): var B = for h in 0..high(t.data): if isFilled(t.data[h].hcode): yield t.data[h].val -proc `[]`*[A, B](t: TableRef[A, B], key: A): B = - ## retrieves the value at ``t[key]``. If `key` is not in `t`, - ## default empty value for the type `B` is returned - ## and no exception is raised. One can check with ``hasKey`` whether the key - ## exists. +proc `[]`*[A, B](t: TableRef[A, B], key: A): var B {.deprecatedGet.} = + ## retrieves the value at ``t[key]``. If `key` is not in `t`, the + ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether + ## the key exists. result = t[][key] -proc mget*[A, B](t: TableRef[A, B], key: A): var B = +proc mget*[A, B](t: TableRef[A, B], key: A): var B {.deprecated.} = ## retrieves the value at ``t[key]``. The value can be modified. - ## If `key` is not in `t`, the ``EInvalidKey`` exception is raised. - t[].mget(key) + ## If `key` is not in `t`, the ``KeyError`` exception is raised. + ## Use ```[]``` instead. + t[][key] + +proc getOrDefault*[A, B](t: TableRef[A, B], key: A): B = getOrDefault(t[], key) proc mgetOrPut*[A, B](t: TableRef[A, B], key: A, val: B): var B = ## retrieves value at ``t[key]`` or puts ``val`` if not present, either way @@ -399,22 +420,26 @@ proc rawGetDeep[A, B](t: OrderedTable[A, B], key: A, hc: var Hash): int {.inline proc rawGet[A, B](t: OrderedTable[A, B], key: A, hc: var Hash): int = rawGetImpl() -proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B = - ## retrieves the value at ``t[key]``. If `key` is not in `t`, - ## default empty value for the type `B` is returned - ## and no exception is raised. One can check with ``hasKey`` whether the key - ## exists. - var hc: Hash - var index = rawGet(t, key, hc) - if index >= 0: result = t.data[index].val +proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B {.deprecatedGet.} = + ## retrieves the value at ``t[key]``. If `key` is not in `t`, the + ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether + ## the key exists. + get(t, key) -proc mget*[A, B](t: var OrderedTable[A, B], key: A): var B = +proc `[]`*[A, B](t: var OrderedTable[A, B], key: A): var B{.deprecatedGet.} = ## retrieves the value at ``t[key]``. The value can be modified. - ## If `key` is not in `t`, the ``EInvalidKey`` exception is raised. - var hc: Hash - var index = rawGet(t, key, hc) - if index >= 0: result = t.data[index].val - else: raise newException(KeyError, "key not found: " & $key) + ## If `key` is not in `t`, the ``KeyError`` exception is raised. + get(t, key) + +proc mget*[A, B](t: var OrderedTable[A, B], key: A): var B {.deprecated.} = + ## retrieves the value at ``t[key]``. The value can be modified. + ## If `key` is not in `t`, the ``KeyError`` exception is raised. + ## Use ```[]``` instead. + get(t, key) + +proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A): B = + getOrDefaultImpl(t, key) + proc hasKey*[A, B](t: OrderedTable[A, B], key: A): bool = ## returns true iff `key` is in the table `t`. @@ -572,17 +597,20 @@ iterator mvalues*[A, B](t: OrderedTableRef[A, B]): var B = forAllOrderedPairs: yield t.data[h].val -proc `[]`*[A, B](t: OrderedTableRef[A, B], key: A): B = - ## retrieves the value at ``t[key]``. If `key` is not in `t`, - ## default empty value for the type `B` is returned - ## and no exception is raised. One can check with ``hasKey`` whether the key - ## exists. +proc `[]`*[A, B](t: OrderedTableRef[A, B], key: A): var B = + ## retrieves the value at ``t[key]``. If `key` is not in `t`, the + ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether + ## the key exists. result = t[][key] -proc mget*[A, B](t: OrderedTableRef[A, B], key: A): var B = +proc mget*[A, B](t: OrderedTableRef[A, B], key: A): var B {.deprecated.} = ## retrieves the value at ``t[key]``. The value can be modified. - ## If `key` is not in `t`, the ``EInvalidKey`` exception is raised. - result = t[].mget(key) + ## If `key` is not in `t`, the ``KeyError`` exception is raised. + ## Use ```[]``` instead. + result = t[][key] + +proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A): B = + getOrDefault(t[], key) proc mgetOrPut*[A, B](t: OrderedTableRef[A, B], key: A, val: B): var B = ## retrieves value at ``t[key]`` or puts ``val`` if not present, either way @@ -683,19 +711,35 @@ proc rawGet[A](t: CountTable[A], key: A): int = h = nextTry(h, high(t.data)) result = -1 - h # < 0 => MISSING; insert idx = -1 - result -proc `[]`*[A](t: CountTable[A], key: A): int = - ## retrieves the value at ``t[key]``. If `key` is not in `t`, - ## 0 is returned. One can check with ``hasKey`` whether the key - ## exists. +template ctget(t, key: untyped): untyped {.immediate.} = var index = rawGet(t, key) if index >= 0: result = t.data[index].val + else: + when compiles($key): + raise newException(KeyError, "key not found: " & $key) + else: + raise newException(KeyError, "key not found") + +proc `[]`*[A](t: CountTable[A], key: A): int {.deprecatedGet.} = + ## retrieves the value at ``t[key]``. If `key` is not in `t`, + ## the ``KeyError`` exception is raised. One can check with ``hasKey`` + ## whether the key exists. + ctget(t, key) -proc mget*[A](t: var CountTable[A], key: A): var int = +proc `[]`*[A](t: var CountTable[A], key: A): var int {.deprecatedGet.} = ## retrieves the value at ``t[key]``. The value can be modified. - ## If `key` is not in `t`, the ``EInvalidKey`` exception is raised. + ## If `key` is not in `t`, the ``KeyError`` exception is raised. + ctget(t, key) + +proc mget*[A](t: var CountTable[A], key: A): var int {.deprecated.} = + ## retrieves the value at ``t[key]``. The value can be modified. + ## If `key` is not in `t`, the ``KeyError`` exception is raised. + ## Use ```[]``` instead. + ctget(t, key) + +proc getOrDefault*[A](t: CountTable[A], key: A): int = var index = rawGet(t, key) if index >= 0: result = t.data[index].val - else: raise newException(KeyError, "key not found: " & $key) proc hasKey*[A](t: CountTable[A], key: A): bool = ## returns true iff `key` is in the table `t`. @@ -831,16 +875,19 @@ iterator mvalues*[A](t: CountTableRef[A]): var int = for h in 0..high(t.data): if t.data[h].val != 0: yield t.data[h].val -proc `[]`*[A](t: CountTableRef[A], key: A): int = - ## retrieves the value at ``t[key]``. If `key` is not in `t`, - ## 0 is returned. One can check with ``hasKey`` whether the key - ## exists. +proc `[]`*[A](t: CountTableRef[A], key: A): var int {.deprecatedGet.} = + ## retrieves the value at ``t[key]``. The value can be modified. + ## If `key` is not in `t`, the ``KeyError`` exception is raised. result = t[][key] -proc mget*[A](t: CountTableRef[A], key: A): var int = +proc mget*[A](t: CountTableRef[A], key: A): var int {.deprecated.} = ## retrieves the value at ``t[key]``. The value can be modified. - ## If `key` is not in `t`, the ``EInvalidKey`` exception is raised. - result = t[].mget(key) + ## If `key` is not in `t`, the ``KeyError`` exception is raised. + ## Use ```[]``` instead. + result = t[][key] + +proc getOrDefault*[A](t: CountTableRef[A], key: A): int = + getOrDefaultImpl(t, key) proc hasKey*[A](t: CountTableRef[A], key: A): bool = ## returns true iff `key` is in the table `t`. diff --git a/lib/pure/hashes.nim b/lib/pure/hashes.nim index 61c16129b..11af81149 100644 --- a/lib/pure/hashes.nim +++ b/lib/pure/hashes.nim @@ -8,9 +8,10 @@ # ## This module implements efficient computations of hash values for diverse -## Nim types. All the procs are based on these two building blocks: the `!& -## proc <#!&>`_ used to start or mix a hash value, and the `!$ proc <#!$>`_ -## used to *finish* the hash value. If you want to implement hash procs for +## Nim types. All the procs are based on these two building blocks: +## - `!& proc <#!&>`_ used to start or mix a hash value, and +## - `!$ proc <#!$>`_ used to *finish* the hash value. +## If you want to implement hash procs for ## your custom types you will end up writing the following kind of skeleton of ## code: ## @@ -108,7 +109,7 @@ proc hash*(x: int): Hash {.inline.} = result = x proc hash*(x: int64): Hash {.inline.} = - ## efficient hashing of integers + ## efficient hashing of int64 integers result = toU32(x) proc hash*(x: char): Hash {.inline.} = @@ -126,6 +127,16 @@ proc hash*(x: string): Hash = h = h !& ord(x[i]) result = !$h +proc hash*(sBuf: string, sPos, ePos: int): Hash = + ## efficient hashing of a string buffer, from starting + ## position `sPos` to ending position `ePos` + ## + ## ``hash(myStr, 0, myStr.high)`` is equivalent to ``hash(myStr)`` + var h: Hash = 0 + for i in sPos..ePos: + h = h !& ord(sBuf[i]) + result = !$h + proc hashIgnoreStyle*(x: string): Hash = ## efficient hashing of strings; style is ignored var h: Hash = 0 @@ -145,6 +156,27 @@ proc hashIgnoreStyle*(x: string): Hash = result = !$h +proc hashIgnoreStyle*(sBuf: string, sPos, ePos: int): Hash = + ## efficient hashing of a string buffer, from starting + ## position `sPos` to ending position `ePos`; style is ignored + ## + ## ``hashIgnoreStyle(myBuf, 0, myBuf.high)`` is equivalent + ## to ``hashIgnoreStyle(myBuf)`` + var h: Hash = 0 + var i = sPos + while i <= ePos: + var c = sBuf[i] + if c == '_': + inc(i) + elif isMagicIdentSeparatorRune(cstring(sBuf), i): + inc(i, magicIdentSeparatorRuneByteWidth) + else: + if c in {'A'..'Z'}: + c = chr(ord(c) + (ord('a') - ord('A'))) # toLower() + h = h !& ord(c) + inc(i) + result = !$h + proc hashIgnoreCase*(x: string): Hash = ## efficient hashing of strings; case is ignored var h: Hash = 0 @@ -155,7 +187,22 @@ proc hashIgnoreCase*(x: string): Hash = h = h !& ord(c) result = !$h +proc hashIgnoreCase*(sBuf: string, sPos, ePos: int): Hash = + ## efficient hashing of a string buffer, from starting + ## position `sPos` to ending position `ePos`; case is ignored + ## + ## ``hashIgnoreCase(myBuf, 0, myBuf.high)`` is equivalent + ## to ``hashIgnoreCase(myBuf)`` + var h: Hash = 0 + for i in sPos..ePos: + var c = sBuf[i] + if c in {'A'..'Z'}: + c = chr(ord(c) + (ord('a') - ord('A'))) # toLower() + h = h !& ord(c) + result = !$h + proc hash*(x: float): Hash {.inline.} = + ## efficient hashing of floats. var y = x + 1.0 result = cast[ptr Hash](addr(y))[] @@ -173,10 +220,29 @@ proc hash*[T: tuple](x: T): Hash = result = !$result proc hash*[A](x: openArray[A]): Hash = + ## efficient hashing of arrays and sequences. for it in items(x): result = result !& hash(it) result = !$result +proc hash*[A](aBuf: openArray[A], sPos, ePos: int): Hash = + ## efficient hashing of portions of arrays and sequences. + ## + ## ``hash(myBuf, 0, myBuf.high)`` is equivalent to ``hash(myBuf)`` + for i in sPos..ePos: + result = result !& hash(aBuf[i]) + result = !$result + proc hash*[A](x: set[A]): Hash = + ## efficient hashing of sets. for it in items(x): result = result !& hash(it) result = !$result +when isMainModule: + doAssert( hash("aa bb aaaa1234") == hash("aa bb aaaa1234", 0, 13) ) + doAssert( hashIgnoreCase("aa bb aaaa1234") == hash("aa bb aaaa1234") ) + doAssert( hashIgnoreStyle("aa bb aaaa1234") == hashIgnoreCase("aa bb aaaa1234") ) + let xx = @['H','e','l','l','o'] + let ss = "Hello" + doAssert( hash(xx) == hash(ss) ) + doAssert( hash(xx) == hash(xx, 0, xx.high) ) + doAssert( hash(ss) == hash(ss, 0, ss.high) ) diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim index 7a900daae..aa55b5ade 100644 --- a/lib/pure/logging.nim +++ b/lib/pure/logging.nim @@ -77,7 +77,7 @@ type ## console FileLogger* = ref object of Logger ## logger that writes the messages to a file - f: File + file*: File ## the wrapped file. RollingFileLogger* = ref object of FileLogger ## logger that writes the ## messages to a file and @@ -92,7 +92,9 @@ type {.deprecated: [TLevel: Level, PLogger: Logger, PConsoleLogger: ConsoleLogger, PFileLogger: FileLogger, PRollingFileLogger: RollingFileLogger].} -proc substituteLog(frmt: string, level: Level, args: varargs[string, `$`]): string = +proc substituteLog*(frmt: string, level: Level, args: varargs[string, `$`]): string = + ## Format a log message using the ``frmt`` format string, ``level`` and varargs. + ## See the module documentation for the format string syntax. var msgLen = 0 for arg in args: msgLen += arg.len @@ -124,7 +126,7 @@ proc substituteLog(frmt: string, level: Level, args: varargs[string, `$`]): stri method log*(logger: Logger, level: Level, args: varargs[string, `$`]) {. raises: [Exception], - tags: [TimeEffect, WriteIOEffect, ReadIOEffect].} = + tags: [TimeEffect, WriteIOEffect, ReadIOEffect], base.} = ## Override this method in custom loggers. Default implementation does ## nothing. discard @@ -133,15 +135,17 @@ method log*(logger: ConsoleLogger, level: Level, args: varargs[string, `$`]) = ## Logs to the console using ``logger`` only. if level >= logger.levelThreshold: writeLine(stdout, substituteLog(logger.fmtStr, level, args)) + if level in {lvlError, lvlFatal}: flushFile(stdout) method log*(logger: FileLogger, level: Level, args: varargs[string, `$`]) = ## Logs to a file using ``logger`` only. if level >= logger.levelThreshold: - writeLine(logger.f, substituteLog(logger.fmtStr, level, args)) + writeLine(logger.file, substituteLog(logger.fmtStr, level, args)) + if level in {lvlError, lvlFatal}: flushFile(logger.file) proc defaultFilename*(): string = ## Returns the default filename for a logger. - var (path, name, ext) = splitFile(getAppFilename()) + var (path, name, _) = splitFile(getAppFilename()) result = changeFileExt(path / name, "log") proc newConsoleLogger*(levelThreshold = lvlAll, fmtStr = defaultFmtStr): ConsoleLogger = @@ -160,14 +164,14 @@ proc newFileLogger*(filename = defaultFilename(), ## (-1: use system defaults, 0: unbuffered, >0: fixed buffer size). new(result) result.levelThreshold = levelThreshold - result.f = open(filename, mode, bufSize = bufSize) + result.file = open(filename, mode, bufSize = bufSize) result.fmtStr = fmtStr # ------ proc countLogLines(logger: RollingFileLogger): int = result = 0 - for line in logger.f.lines(): + for line in logger.file.lines(): result.inc() proc countFiles(filename: string): int = @@ -200,7 +204,7 @@ proc newRollingFileLogger*(filename = defaultFilename(), result.fmtStr = fmtStr result.maxLines = maxLines result.bufSize = bufSize - result.f = open(filename, mode, bufSize=result.bufSize) + result.file = open(filename, mode, bufSize=result.bufSize) result.curLine = 0 result.baseName = filename result.baseMode = mode @@ -222,13 +226,14 @@ method log*(logger: RollingFileLogger, level: Level, args: varargs[string, `$`]) ## Logs to a file using rolling ``logger`` only. if level >= logger.levelThreshold: if logger.curLine >= logger.maxLines: - logger.f.close() + logger.file.close() rotate(logger) logger.logFiles.inc logger.curLine = 0 - logger.f = open(logger.baseName, logger.baseMode, bufSize = logger.bufSize) + logger.file = open(logger.baseName, logger.baseMode, bufSize = logger.bufSize) - writeLine(logger.f, substituteLog(logger.fmtStr, level, args)) + writeLine(logger.file, substituteLog(logger.fmtStr, level, args)) + if level in {lvlError, lvlFatal}: flushFile(logger.file) logger.curLine.inc # -------- diff --git a/lib/pure/marshal.nim b/lib/pure/marshal.nim index 173cd1e81..134581a06 100644 --- a/lib/pure/marshal.nim +++ b/lib/pure/marshal.nim @@ -176,7 +176,7 @@ proc loadAny(p: var JsonParser, a: Any, t: var Table[BiggestInt, pointer]) = setPointer(a, nil) next(p) of jsonInt: - setPointer(a, t[p.getInt]) + setPointer(a, t.getOrDefault(p.getInt)) next(p) of jsonArrayStart: next(p) diff --git a/lib/pure/md5.nim b/lib/pure/md5.nim index 5ee301b15..44b9ed0d4 100644 --- a/lib/pure/md5.nim +++ b/lib/pure/md5.nim @@ -9,8 +9,6 @@ ## Module for computing MD5 checksums. -import unsigned - type MD5State = array[0..3, uint32] MD5Block = array[0..15, uint32] diff --git a/lib/pure/mimetypes.nim b/lib/pure/mimetypes.nim index 642419e64..1e315afb4 100644 --- a/lib/pure/mimetypes.nim +++ b/lib/pure/mimetypes.nim @@ -499,7 +499,7 @@ proc newMimetypes*(): MimeDB = proc getMimetype*(mimedb: MimeDB, ext: string, default = "text/plain"): string = ## Gets mimetype which corresponds to ``ext``. Returns ``default`` if ``ext`` ## could not be found. - result = mimedb.mimes[ext] + result = mimedb.mimes.getOrDefault(ext) if result == "": return default diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim index 667ca82d7..e9f5bee0a 100644 --- a/lib/pure/ospaths.nim +++ b/lib/pure/ospaths.nim @@ -26,8 +26,10 @@ when not declared(getEnv) or defined(nimscript): ## to an environment variable ReadDirEffect* = object of ReadIOEffect ## effect that denotes a write - ## operation to the directory structure - WriteDirEffect* = object of WriteIOEffect ## effect that denotes a write operation to + ## operation to the directory + ## structure + WriteDirEffect* = object of WriteIOEffect ## effect that denotes a write + ## operation to ## the directory structure OSErrorCode* = distinct int32 ## Specifies an OS Error Code. @@ -63,13 +65,13 @@ when not declared(getEnv) or defined(nimscript): AltSep* = '/' ## An alternative character used by the operating system to separate ## pathname components, or the same as `DirSep` if only one separator - ## character exists. This is set to '/' on Windows systems where `DirSep` - ## is a backslash. + ## character exists. This is set to '/' on Windows systems + ## where `DirSep` is a backslash. PathSep* = ':' ## The character conventionally used by the operating system to separate - ## search patch components (as in PATH), such as ':' for POSIX or ';' for - ## Windows. + ## search patch components (as in PATH), such as ':' for POSIX + ## or ';' for Windows. FileSystemCaseSensitive* = true ## true if the file system is case sensitive, false otherwise. Used by @@ -104,7 +106,8 @@ when not declared(getEnv) or defined(nimscript): # MacOS directory separator is a colon ":" which is the only character not # allowed in filenames. # - # A path containing no colon or which begins with a colon is a partial path. + # A path containing no colon or which begins with a colon is a partial + # path. # E.g. ":kalle:petter" ":kalle" "kalle" # # All other paths are full (absolute) paths. E.g. "HD:kalle:" "HD:" @@ -206,9 +209,9 @@ when not declared(getEnv) or defined(nimscript): proc joinPath*(parts: varargs[string]): string {.noSideEffect, rtl, extern: "nos$1OpenArray".} = - ## The same as `joinPath(head, tail)`, but works with any number of directory - ## parts. You need to pass at least one element or the proc will assert in - ## debug builds and crash on release builds. + ## The same as `joinPath(head, tail)`, but works with any number of + ## directory parts. You need to pass at least one element or the proc + ## will assert in debug builds and crash on release builds. result = parts[0] for i in 1..high(parts): result = joinPath(result, parts[i]) @@ -316,8 +319,8 @@ when not declared(getEnv) or defined(nimscript): if inclusive: yield path proc `/../` * (head, tail: string): string {.noSideEffect.} = - ## The same as ``parentDir(head) / tail`` unless there is no parent directory. - ## Then ``head / tail`` is performed instead. + ## The same as ``parentDir(head) / tail`` unless there is no parent + ## directory. Then ``head / tail`` is performed instead. let sepPos = parentDirPos(head) if sepPos >= 0: result = substr(head, 0, sepPos-1) / tail @@ -500,7 +503,8 @@ when defined(nimdoc) and not declared(os): proc existsFile(x: string): bool = discard when declared(getEnv) or defined(nimscript): - proc getHomeDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect, ReadIOEffect].} = + proc getHomeDir*(): string {.rtl, extern: "nos$1", + tags: [ReadEnvEffect, ReadIOEffect].} = ## Returns the home directory of the current user. ## ## This proc is wrapped by the expandTilde proc for the convenience of @@ -508,18 +512,21 @@ when declared(getEnv) or defined(nimscript): when defined(windows): return string(getEnv("USERPROFILE")) & "\\" else: return string(getEnv("HOME")) & "/" - proc getConfigDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect, ReadIOEffect].} = + proc getConfigDir*(): string {.rtl, extern: "nos$1", + tags: [ReadEnvEffect, ReadIOEffect].} = ## Returns the config directory of the current user for applications. when defined(windows): return string(getEnv("APPDATA")) & "\\" else: return string(getEnv("HOME")) & "/.config/" - proc getTempDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect].} = + proc getTempDir*(): string {.rtl, extern: "nos$1", + tags: [ReadEnvEffect, ReadIOEffect].} = ## Returns the temporary directory of the current user for applications to ## save temporary files in. when defined(windows): return string(getEnv("TEMP")) & "\\" else: return "/tmp/" - proc expandTilde*(path: string): string {.tags: [ReadEnvEffect, ReadIOEffect].} = + proc expandTilde*(path: string): string {. + tags: [ReadEnvEffect, ReadIOEffect].} = ## Expands a path starting with ``~/`` to a full path. ## ## If `path` starts with the tilde character and is followed by `/` or `\\` @@ -527,8 +534,8 @@ when declared(getEnv) or defined(nimscript): ## the getHomeDir() proc, otherwise the input path will be returned without ## modification. ## - ## The behaviour of this proc is the same on the Windows platform despite not - ## having this convention. Example: + ## The behaviour of this proc is the same on the Windows platform despite + ## not having this convention. Example: ## ## .. code-block:: nim ## let configFile = expandTilde("~" / "appname.cfg") @@ -549,7 +556,8 @@ when declared(getEnv) or defined(nimscript): yield substr(s, first, last-1) inc(last) - proc findExe*(exe: string): string {.tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect].} = + proc findExe*(exe: string): string {. + tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect].} = ## 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` diff --git a/lib/pure/parseopt.nim b/lib/pure/parseopt.nim new file mode 100644 index 000000000..218f5ab81 --- /dev/null +++ b/lib/pure/parseopt.nim @@ -0,0 +1,178 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2015 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module provides the standard Nim command line parser. +## It supports one convenience iterator over all command line options and some +## lower-level features. +## +## Supported syntax: +## +## 1. short options - ``-abcd``, where a, b, c, d are names +## 2. long option - ``--foo:bar``, ``--foo=bar`` or ``--foo`` +## 3. argument - everything else + +{.push debugger: off.} + +include "system/inclrtl" + +import + os, strutils + +type + CmdLineKind* = enum ## the detected command line token + cmdEnd, ## end of command line reached + cmdArgument, ## argument detected + cmdLongOption, ## a long option ``--option`` detected + cmdShortOption ## a short option ``-c`` detected + OptParser* = + object of RootObj ## this object implements the command line parser + cmd: string + pos: int + inShortState: bool + kind*: CmdLineKind ## the dected command line token + key*, val*: TaintedString ## key and value pair; ``key`` is the option + ## or the argument, ``value`` is not "" if + ## the option was given a value + +{.deprecated: [TCmdLineKind: CmdLineKind, TOptParser: OptParser].} + +proc parseWord(s: string, i: int, w: var string, + delim: set[char] = {'\x09', ' ', '\0'}): int = + result = i + if s[result] == '\"': + inc(result) + while not (s[result] in {'\0', '\"'}): + add(w, s[result]) + inc(result) + if s[result] == '\"': inc(result) + else: + while not (s[result] in delim): + add(w, s[result]) + inc(result) + +when declared(os.paramCount): + proc quote(s: string): string = + if find(s, {' ', '\t'}) >= 0 and s[0] != '"': + if s[0] == '-': + result = newStringOfCap(s.len) + var i = parseWord(s, 0, result, {'\0', ' ', '\x09', ':', '='}) + if s[i] in {':','='}: + result.add s[i] + inc i + result.add '"' + while i < s.len: + result.add s[i] + inc i + result.add '"' + else: + result = '"' & s & '"' + else: + result = s + + # we cannot provide this for NimRtl creation on Posix, because we can't + # access the command line arguments then! + + proc initOptParser*(cmdline = ""): OptParser = + ## inits the option parser. If ``cmdline == ""``, the real command line + ## (as provided by the ``OS`` module) is taken. + result.pos = 0 + result.inShortState = false + if cmdline != "": + result.cmd = cmdline + else: + result.cmd = "" + for i in countup(1, paramCount()): + result.cmd.add quote(paramStr(i).string) + result.cmd.add ' ' + result.kind = cmdEnd + result.key = TaintedString"" + result.val = TaintedString"" + +proc handleShortOption(p: var OptParser) = + var i = p.pos + p.kind = cmdShortOption + add(p.key.string, p.cmd[i]) + inc(i) + p.inShortState = true + while p.cmd[i] in {'\x09', ' '}: + inc(i) + p.inShortState = false + if p.cmd[i] in {':', '='}: + inc(i) + p.inShortState = false + while p.cmd[i] in {'\x09', ' '}: inc(i) + i = parseWord(p.cmd, i, p.val.string) + if p.cmd[i] == '\0': p.inShortState = false + p.pos = i + +proc next*(p: var OptParser) {.rtl, extern: "npo$1".} = + ## parses the first or next option; ``p.kind`` describes what token has been + ## parsed. ``p.key`` and ``p.val`` are set accordingly. + var i = p.pos + while p.cmd[i] in {'\x09', ' '}: inc(i) + p.pos = i + setLen(p.key.string, 0) + setLen(p.val.string, 0) + if p.inShortState: + handleShortOption(p) + return + case p.cmd[i] + of '\0': + p.kind = cmdEnd + of '-': + inc(i) + if p.cmd[i] == '-': + p.kind = cmdLongoption + inc(i) + i = parseWord(p.cmd, i, p.key.string, {'\0', ' ', '\x09', ':', '='}) + while p.cmd[i] in {'\x09', ' '}: inc(i) + if p.cmd[i] in {':', '='}: + inc(i) + while p.cmd[i] in {'\x09', ' '}: inc(i) + p.pos = parseWord(p.cmd, i, p.val.string) + else: + p.pos = i + else: + p.pos = i + handleShortOption(p) + else: + p.kind = cmdArgument + p.pos = parseWord(p.cmd, i, p.key.string) + +proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo$1".} = + ## retrieves the rest of the command line that has not been parsed yet. + result = strip(substr(p.cmd, p.pos, len(p.cmd) - 1)).TaintedString + +when declared(initOptParser): + iterator getopt*(): tuple[kind: CmdLineKind, key, val: TaintedString] = + ## This is an convenience iterator for iterating over the command line. + ## This uses the OptParser object. Example: + ## + ## .. code-block:: nim + ## var + ## filename = "" + ## for kind, key, val in getopt(): + ## case kind + ## of cmdArgument: + ## filename = key + ## of cmdLongOption, cmdShortOption: + ## case key + ## of "help", "h": writeHelp() + ## of "version", "v": writeVersion() + ## of cmdEnd: assert(false) # cannot happen + ## if filename == "": + ## # no filename has been given, so we show the help: + ## writeHelp() + var p = initOptParser() + while true: + next(p) + if p.kind == cmdEnd: break + yield (p.kind, p.key, p.val) + +{.pop.} diff --git a/lib/pure/scgi.nim b/lib/pure/scgi.nim index 1d54b4591..711e4a897 100644 --- a/lib/pure/scgi.nim +++ b/lib/pure/scgi.nim @@ -145,8 +145,8 @@ proc next*(s: var ScgiState, timeout: int = -1): bool = L = L * 10 + ord(d) - ord('0') recvBuffer(s, L+1) s.headers = parseHeaders(s.input, L) - if s.headers["SCGI"] != "1": raiseScgiError("SCGI Version 1 expected") - L = parseInt(s.headers["CONTENT_LENGTH"]) + if s.headers.getOrDefault("SCGI") != "1": raiseScgiError("SCGI Version 1 expected") + L = parseInt(s.headers.getOrDefault("CONTENT_LENGTH")) recvBuffer(s, L) return true @@ -221,10 +221,10 @@ proc handleClientRead(client: AsyncClient, s: AsyncScgiState) = case ret of ReadFullLine: client.headers = parseHeaders(client.input, client.input.len-1) - if client.headers["SCGI"] != "1": raiseScgiError("SCGI Version 1 expected") + if client.headers.getOrDefault("SCGI") != "1": raiseScgiError("SCGI Version 1 expected") client.input = "" # For next part - let contentLen = parseInt(client.headers["CONTENT_LENGTH"]) + let contentLen = parseInt(client.headers.getOrDefault("CONTENT_LENGTH")) if contentLen > 0: client.mode = ClientReadContent else: @@ -232,7 +232,8 @@ proc handleClientRead(client: AsyncClient, s: AsyncScgiState) = checkCloseSocket(client) of ReadPartialLine, ReadDisconnected, ReadNone: return of ClientReadContent: - let L = parseInt(client.headers["CONTENT_LENGTH"])-client.input.len + let L = parseInt(client.headers.getOrDefault("CONTENT_LENGTH")) - + client.input.len if L > 0: let ret = recvBufferAsync(client, L) case ret diff --git a/lib/pure/strtabs.nim b/lib/pure/strtabs.nim index 1ce9067a7..1c761cd92 100644 --- a/lib/pure/strtabs.nim +++ b/lib/pure/strtabs.nim @@ -101,21 +101,31 @@ proc rawGet(t: StringTableRef, key: string): int = h = nextTry(h, high(t.data)) result = - 1 -proc `[]`*(t: StringTableRef, key: string): string {.rtl, extern: "nstGet".} = - ## retrieves the value at ``t[key]``. If `key` is not in `t`, "" is returned - ## and no exception is raised. One can check with ``hasKey`` whether the key - ## exists. +template get(t: StringTableRef, key: string): stmt {.immediate.} = var index = rawGet(t, key) if index >= 0: result = t.data[index].val - else: result = "" + else: + when compiles($key): + raise newException(KeyError, "key not found: " & $key) + else: + raise newException(KeyError, "key not found") -proc mget*(t: StringTableRef, key: string): var string {. - rtl, extern: "nstTake".} = +proc `[]`*(t: StringTableRef, key: string): var string {. + rtl, extern: "nstTake", deprecatedGet.} = ## retrieves the location at ``t[key]``. If `key` is not in `t`, the - ## ``KeyError`` exception is raised. + ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether + ## the key exists. + get(t, key) + +proc mget*(t: StringTableRef, key: string): var string {.deprecated.} = + ## retrieves the location at ``t[key]``. If `key` is not in `t`, the + ## ``KeyError`` exception is raised. Use ```[]``` instead. + get(t, key) + +proc getOrDefault*(t: StringTableRef; key: string): string = var index = rawGet(t, key) if index >= 0: result = t.data[index].val - else: raise newException(KeyError, "key does not exist: " & key) + else: result = "" proc hasKey*(t: StringTableRef, key: string): bool {.rtl, extern: "nst$1".} = ## returns true iff `key` is in the table `t`. @@ -152,7 +162,7 @@ proc raiseFormatException(s: string) = raise e proc getValue(t: StringTableRef, flags: set[FormatFlag], key: string): string = - if hasKey(t, key): return t[key] + if hasKey(t, key): return t.getOrDefault(key) # hm difficult: assume safety in taint mode here. XXX This is dangerous! if useEnvironment in flags: result = os.getEnv(key).string else: result = "" @@ -248,7 +258,7 @@ when isMainModule: assert x["k"] == "v" assert x["11"] == "22" assert x["565"] == "67" - x.mget("11") = "23" + x["11"] = "23" assert x["11"] == "23" x.clear(modeCaseInsensitive) diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index eacea72e4..a78fed4b9 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -60,6 +60,132 @@ const ## doAssert "01234".find(invalid) == -1 ## doAssert "01A34".find(invalid) == 2 +proc isAlpha*(c: char): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsAlphaChar".}= + ## Checks whether or not `c` is alphabetical. + ## + ## This checks a-z, A-Z ASCII characters only. + return c in Letters + +proc isAlphaNumeric*(c: char): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsAlphaNumericChar".}= + ## Checks whether or not `c` is alphanumeric. + ## + ## This checks a-z, A-Z, 0-9 ASCII characters only. + return c in Letters or c in Digits + +proc isDigit*(c: char): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsDigitChar".}= + ## Checks whether or not `c` is a number. + ## + ## This checks 0-9 ASCII characters only. + return c in Digits + +proc isSpace*(c: char): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsSpaceChar".}= + ## Checks whether or not `c` is a whitespace character. + return c in Whitespace + +proc isLower*(c: char): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsLowerChar".}= + ## Checks whether or not `c` is a lower case character. + ## + ## This checks ASCII characters only. + return c in {'a'..'z'} + +proc isUpper*(c: char): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsUpperChar".}= + ## Checks whether or not `c` is an upper case character. + ## + ## This checks ASCII characters only. + return c in {'A'..'Z'} + +proc isAlpha*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsAlphaStr".}= + ## Checks whether or not `s` is alphabetical. + ## + ## This checks a-z, A-Z ASCII characters only. + ## Returns true if all characters in `s` are + ## alphabetic and there is at least one character + ## in `s`. + if s.len() == 0: + return false + + result = true + for c in s: + result = c.isAlpha() and result + +proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsAlphaNumericStr".}= + ## Checks whether or not `s` is alphanumeric. + ## + ## This checks a-z, A-Z, 0-9 ASCII characters only. + ## Returns true if all characters in `s` are + ## alpanumeric and there is at least one character + ## in `s`. + if s.len() == 0: + return false + + result = true + for c in s: + result = c.isAlphaNumeric() and result + +proc isDigit*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsDigitStr".}= + ## Checks whether or not `s` is a numeric value. + ## + ## This checks 0-9 ASCII characters only. + ## Returns true if all characters in `s` are + ## numeric and there is at least one character + ## in `s`. + if s.len() == 0: + return false + + result = true + for c in s: + result = c.isDigit() and result + +proc isSpace*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsSpaceStr".}= + ## Checks whether or not `s` is completely whitespace. + ## + ## Returns true if all characters in `s` are whitespace + ## characters and there is at least one character in `s`. + if s.len() == 0: + return false + + result = true + for c in s: + result = c.isSpace() and result + +proc isLower*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsLowerStr".}= + ## Checks whether or not `s` contains all lower case characters. + ## + ## This checks ASCII characters only. + ## Returns true if all characters in `s` are lower case + ## and there is at least one character in `s`. + if s.len() == 0: + return false + + result = true + for c in s: + result = c.isLower() and result + +proc isUpper*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsUpperStr".}= + ## Checks whether or not `s` contains all upper case characters. + ## + ## This checks ASCII characters only. + ## Returns true if all characters in `s` are upper case + ## and there is at least one character in `s`. + if s.len() == 0: + return false + + result = true + for c in s: + result = c.isUpper() and result + proc toLower*(c: char): char {.noSideEffect, procvar, rtl, extern: "nsuToLowerChar".} = ## Converts `c` into lower case. @@ -1526,3 +1652,55 @@ when isMainModule: doAssert strip("sfoofoofoos", trailing = false, chars = {'s'}) == "foofoofoos" doAssert " foo\n bar".indent(4, "Q") == "QQQQ foo\nQQQQ bar" + + doAssert isAlpha('r') + doAssert isAlpha('A') + doAssert(not isAlpha('$')) + + doAssert isAlpha("Rasp") + doAssert isAlpha("Args") + doAssert(not isAlpha("$Tomato")) + + doAssert isAlphaNumeric('3') + doAssert isAlphaNumeric('R') + doAssert(not isAlphaNumeric('!')) + + doAssert isAlphaNumeric("34ABc") + doAssert isAlphaNumeric("Rad") + doAssert isAlphaNumeric("1234") + doAssert(not isAlphaNumeric("@nose")) + + doAssert isDigit('3') + doAssert(not isDigit('a')) + doAssert(not isDigit('%')) + + doAssert isDigit("12533") + doAssert(not isDigit("12.33")) + doAssert(not isDigit("A45b")) + + doAssert isSpace('\t') + doAssert isSpace('\l') + doAssert(not isSpace('A')) + + doAssert isSpace("\t\l \v\r\f") + doAssert isSpace(" ") + doAssert(not isSpace("ABc \td")) + + doAssert isLower('a') + doAssert isLower('z') + doAssert(not isLower('A')) + doAssert(not isLower('5')) + doAssert(not isLower('&')) + + doAssert isLower("abcd") + doAssert(not isLower("abCD")) + doAssert(not isLower("33aa")) + + doAssert isUpper('A') + doAssert(not isUpper('b')) + doAssert(not isUpper('5')) + doAssert(not isUpper('%')) + + doAssert isUpper("ABC") + doAssert(not isUpper("AAcc")) + doAssert(not isUpper("A#$")) diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index a0f7b955e..aca9d51e2 100755 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -150,6 +150,8 @@ template test*(name: expr, body: stmt): stmt {.immediate, dirty.} = try: when declared(testSetupIMPLFlag): testSetupIMPL() body + when declared(testTeardownIMPLFlag): + defer: testTeardownIMPL() except: when not defined(js): @@ -158,7 +160,6 @@ template test*(name: expr, body: stmt): stmt {.immediate, dirty.} = fail() finally: - when declared(testTeardownIMPLFlag): testTeardownIMPL() testDone name, testStatusIMPL proc checkpoint*(msg: string) = diff --git a/lib/pure/xmltree.nim b/lib/pure/xmltree.nim index 7c97a0a56..a9fc8998a 100644 --- a/lib/pure/xmltree.nim +++ b/lib/pure/xmltree.nim @@ -139,11 +139,16 @@ proc delete*(n: XmlNode, i: Natural) {.noSideEffect.} = assert n.k == xnElement n.s.delete(i) -proc mget* (n: var XmlNode, i: int): var XmlNode {.inline.} = +proc `[]`* (n: var XmlNode, i: int): var XmlNode {.inline.} = ## returns the `i`'th child of `n` so that it can be modified assert n.k == xnElement result = n.s[i] +proc mget*(n: var XmlNode, i: int): var XmlNode {.inline, deprecated.} = + ## returns the `i`'th child of `n` so that it can be modified. Use ```[]``` + ## instead. + n[i] + iterator items*(n: XmlNode): XmlNode {.inline.} = ## iterates over any child of `n`. assert n.k == xnElement @@ -152,7 +157,7 @@ iterator items*(n: XmlNode): XmlNode {.inline.} = iterator mitems*(n: var XmlNode): var XmlNode {.inline.} = ## iterates over any child of `n`. assert n.k == xnElement - for i in 0 .. n.len-1: yield mget(n, i) + for i in 0 .. n.len-1: yield n[i] proc attrs*(n: XmlNode): XmlAttributes {.inline.} = ## gets the attributes belonging to `n`. @@ -337,7 +342,7 @@ proc attr*(n: XmlNode, name: string): string = ## Returns "" on failure. assert n.kind == xnElement if n.attrs == nil: return "" - return n.attrs[name] + return n.attrs.getOrDefault(name) proc findAll*(n: XmlNode, tag: string, result: var seq[XmlNode]) = ## Iterates over all the children of `n` returning those matching `tag`. |