summary refs log tree commit diff stats
path: root/lib/pure
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure')
-rw-r--r--lib/pure/asynchttpserver.nim15
-rw-r--r--lib/pure/cgi.nim2
-rw-r--r--lib/pure/collections/critbits.nim32
-rw-r--r--lib/pure/collections/intsets.nim2
-rw-r--r--lib/pure/collections/sequtils.nim296
-rw-r--r--lib/pure/collections/sets.nim11
-rw-r--r--lib/pure/collections/tables.nim155
-rw-r--r--lib/pure/hashes.nim74
-rw-r--r--lib/pure/logging.nim27
-rw-r--r--lib/pure/marshal.nim2
-rw-r--r--lib/pure/md5.nim2
-rw-r--r--lib/pure/mimetypes.nim2
-rw-r--r--lib/pure/ospaths.nim46
-rw-r--r--lib/pure/parseopt.nim178
-rw-r--r--lib/pure/scgi.nim11
-rw-r--r--lib/pure/strtabs.nim32
-rw-r--r--lib/pure/strutils.nim178
-rwxr-xr-xlib/pure/unittest.nim3
-rw-r--r--lib/pure/xmltree.nim11
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`.