Re-org project directory for use with nimble.
authorMahlon E. Smith <mahlon@martini.nu>
Mon, 08 Oct 2018 10:19:07 -0700
changeset 9 c109bf5f2aa4
parent 8 67c1c0c716e8
child 10 92923ab3b779
Re-org project directory for use with nimble.
.hgignore
Makefile
src/tnetstring.nim
tnetstring.nim
tnetstring.nimble
--- a/.hgignore	Sun Apr 30 04:38:52 2017 +0000
+++ b/.hgignore	Mon Oct 08 10:19:07 2018 -0700
@@ -1,4 +1,5 @@
 syntax: glob
 .cache
 tnetstring
+src/tnetstring
 *.html
--- a/Makefile	Sun Apr 30 04:38:52 2017 +0000
+++ b/Makefile	Mon Oct 08 10:19:07 2018 -0700
@@ -1,5 +1,5 @@
 
-FILES = tnetstring.nim
+FILES = src/tnetstring.nim
 
 default: development
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/tnetstring.nim	Mon Oct 08 10:19:07 2018 -0700
@@ -0,0 +1,713 @@
+#
+# Copyright (c) 2015-2018, Mahlon E. Smith <mahlon@martini.nu>
+# All rights reserved.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#
+#     * Neither the name of Mahlon E. Smith nor the names of his
+#       contributors may be used to endorse or promote products derived
+#       from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+## This module implements a simple TNetstring parser and serializer.
+## TNetString stands for "tagged netstring" and is a modification of Dan
+## Bernstein's netstrings specification.  TNetstrings allow for the same data
+## structures as JSON but in a format that is resistant to buffer overflows
+## and backward compatible with original netstrings.  They make no assumptions
+## about string contents, allowing for easy transmission of binary data mixed
+## with strongly typed values.
+
+## See http://cr.yp.to/proto/netstrings.txt and http://tnetstrings.org/ for additional information.
+##
+## This module borrows heavily (in both usage and code) from the nim JSON stdlib
+## (json.nim) -- (c) Copyright 2015 Andreas Rumpf, Dominik Picheta.
+## 
+## Usage example:
+##
+## .. code-block:: nim
+##
+##   let
+##       tnetstr = "52:4:test,3:1.3^4:key2,4:true!6:things,12:1:1#1:2#1:3#]}"
+##       tnetobj = parse_tnetstring( tnetstr )
+##
+##   # tnetobj is now equivalent to the structure:
+##   # @[(key: test, val: 1.3), (key: key2, val: true), (key: things, val: @[1, 2, 3])]
+##
+##   assert( tnetobj.kind == TNetstringObject )
+##   echo tnetobj[ "test" ]
+##   echo tnetobj[ "key2" ]
+##   for item in tnetobj[ "things" ]:
+##       echo item
+##   
+## Results in:
+##
+## .. code-block:: nim
+##
+##   1.3
+##   true
+##   1
+##   2
+##   3
+##
+## This module can also be used to reasonably create a serialized
+## TNetstring, suitable for network transmission:
+##
+## .. code-block:: nim
+##    
+##    let
+##        number  = 1000
+##        list    = @[ "thing1", "thing2" ]
+##        tnettop = newTNetstringArray() # top-level array
+##        tnetsub = newTNetstringArray() # sub array
+##    
+##    tnettop.add( newTNetstringInt(number) )
+##    for item in list:
+##        tnetsub.add( newTNetstringString(item) )
+##    tnettop.add( tnetsub )
+##    
+##    # Equivalent to: @[1000, @[thing1, thing2]]
+##    echo dump_tnetstring( tnettop )
+##
+## Results in:
+##
+## .. code-block:: nim
+##    
+##    29:4:1000#18:6:thing1,6:thing2,]]
+##
+
+import
+    hashes,
+    parseutils,
+    strutils
+
+const version = "0.1.2"
+
+type 
+  TNetstringKind* = enum     ## enumeration of all valid types
+    TNetstringString,        ## a string literal
+    TNetstringInt,           ## an integer literal
+    TNetstringFloat,         ## a float literal
+    TNetstringBool,          ## a ``true`` or ``false`` value
+    TNetstringNull,          ## the value ``null``
+    TNetstringObject,        ## an object: the ``}`` token
+    TNetstringArray          ## an array: the ``]`` token
+
+  TNetstringNode* = ref TNetstringNodeObj
+  TNetstringNodeObj* {.acyclic.} = object
+      extra*: string
+      case kind*: TNetstringKind
+      of TNetstringString:
+          str*: string
+      of TNetstringInt:
+          num*: BiggestInt
+      of TNetstringFloat:
+          fnum*: float
+      of TNetstringBool:
+          bval*: bool
+      of TNetstringNull:
+          nil
+      of TNetstringObject:
+          fields*: seq[ tuple[key: string, val: TNetstringNode] ]
+      of TNetstringArray:
+          elems*: seq[ TNetstringNode ]
+
+  TNetstringParseError* = object of ValueError ## Raised for a TNetstring error
+
+
+proc raiseParseErr*( t: TNetstringNode, msg: string ) {.noinline, noreturn.} =
+  ## Raises a `TNetstringParseError` exception.
+  raise newException( TNetstringParseError, msg )
+
+
+proc newTNetstringString*( s: string ): TNetstringNode =
+    ## Create a new String typed TNetstringNode.
+    new( result )
+    result.kind = TNetstringString
+    result.str = s
+
+
+proc newTNetstringInt*( i: BiggestInt ): TNetstringNode =
+    ## Create a new Integer typed TNetstringNode.
+    new( result )
+    result.kind = TNetstringInt
+    result.num = i
+
+
+proc newTNetstringFloat*( f: float ): TNetstringNode =
+    ## Create a new Float typed TNetstringNode.
+    new( result )
+    result.kind = TNetstringFloat
+    result.fnum = f
+
+
+proc newTNetstringBool*( b: bool ): TNetstringNode =
+    ## Create a new Boolean typed TNetstringNode.
+    new( result )
+    result.kind = TNetstringBool
+    result.bval = b
+
+
+proc newTNetstringNull*(): TNetstringNode =
+    ## Create a new nil typed TNetstringNode.
+    new( result )
+    result.kind = TNetstringNull
+
+
+proc newTNetstringObject*(): TNetstringNode =
+    ## Create a new Object typed TNetstringNode.
+    new( result )
+    result.kind = TNetstringObject
+    result.fields = @[]
+
+
+proc newTNetstringArray*(): TNetstringNode =
+    ## Create a new Array typed TNetstringNode.
+    new( result )
+    result.kind = TNetstringArray
+    result.elems = @[]
+
+
+proc getStr*( node: TNetstringNode, default: string = "" ): string =
+    ## Retrieves the string value of a `TNetstringString TNetstringNodee`.
+    ## Returns ``default`` if ``node`` is not a ``TNetstringString``.
+    if node.kind != TNetstringString: return default
+    return node.str
+
+
+proc getInt*( node: TNetstringNode, default: BiggestInt = 0 ): BiggestInt =
+    ## Retrieves the int value of a `TNetstringInt TNetstringNode`.
+    ## Returns ``default`` if ``node`` is not a ``TNetstringInt``.
+    if node.kind != TNetstringInt: return default
+    return node.num
+
+
+proc getFloat*( node: TNetstringNode, default: float = 0.0 ): float =
+    ## Retrieves the float value of a `TNetstringFloat TNetstringNode`.
+    ## Returns ``default`` if ``node`` is not a ``TNetstringFloat``.
+    if node.kind != TNetstringFloat: return default
+    return node.fnum
+
+
+proc getBool*( node: TNetstringNode, default: bool = false ): bool =
+    ## Retrieves the bool value of a `TNetstringBool TNetstringNode`.
+    ## Returns ``default`` if ``node`` is not a ``TNetstringBool``.
+    if node.kind != TNetstringBool: return default
+    return node.bval
+
+
+proc getFields*( node: TNetstringNode,
+    default: seq[tuple[key: string, val: TNetstringNode]] = @[] ):
+        seq[tuple[key: string, val: TNetstringNode]] =
+    ## Retrieves the key, value pairs of a `TNetstringObject TNetstringNode`.
+    ## Returns ``default`` if ``node`` is not a ``TNetstringObject``.
+    if node.kind != TNetstringObject: return default
+    return node.fields
+
+
+proc getElems*( node: TNetstringNode, default: seq[TNetstringNode] = @[] ): seq[TNetstringNode] =
+    ## Retrieves the values of a `TNetstringArray TNetstringNode`.
+    ## Returns ``default`` if ``node`` is not a ``TNetstringArray``.
+    if node.kind != TNetstringArray: return default
+    return node.elems
+
+
+proc parse_tnetstring*( data: string ): TNetstringNode =
+    ## Given an encoded tnetstring, parse and return a TNetstringNode.
+    var
+        length:  int
+        kind:    char
+        payload: string
+        extra:   string
+
+    let sep_pos = data.skipUntil( ':' )
+    if sep_pos == data.len: raiseParseErr( result, "Invalid data: No separator token found." )
+
+    try:
+        length       = data[ 0 .. sep_pos - 1 ].parseInt
+        kind         = data[ sep_pos + length + 1 ]
+        payload      = data[ sep_pos + 1 .. sep_pos + length ]
+        extra        = data[ sep_pos + length + 2 .. ^1 ]
+
+    except ValueError, IndexError:
+        var msg = getCurrentExceptionMsg()
+        raiseParseErr( result, msg )
+
+    case kind:
+        of ',':
+            result = newTNetstringString( payload )
+
+        of '#':
+            try:
+                result = newTNetstringInt( payload.parseBiggestInt )
+            except ValueError:
+                var msg = getCurrentExceptionMsg()
+                raiseParseErr( result, msg )
+
+        of '^':
+            try:
+                result = newTNetstringFloat( payload.parseFloat )
+            except ValueError:
+                var msg = getCurrentExceptionMsg()
+                raiseParseErr( result, msg )
+
+        of '!':
+            result = newTNetstringBool( payload == "true" )
+
+        of '~':
+            if length != 0: raiseParseErr( result, "Invalid data: Payload must be 0 length for null." )
+            result = newTNetstringNull()
+            
+        of ']':
+            result = newTNetstringArray()
+
+            var subnode = parse_tnetstring( payload )
+            result.elems.add( subnode )
+
+            while subnode.extra != "":
+                subnode = parse_tnetstring( subnode.extra )
+                result.elems.add( subnode )
+
+        of '}':
+            result = newTNetstringObject()
+            var key = parse_tnetstring( payload )
+
+            if ( key.extra == "" ): raiseParseErr( result, "Invalid data: Unbalanced tuple." )
+            if ( key.kind != TNetstringString ): raiseParseErr( result, "Invalid data: Object keys must be strings." )
+
+            var value = parse_tnetstring( key.extra )
+            result.fields.add( (key: key.str, val: value) )
+
+            while value.extra != "":
+                var subkey = parse_tnetstring( value.extra )
+                if ( subkey.extra == "" ): raiseParseErr( result, "Invalid data: Unbalanced tuple." )
+                if ( subkey.kind != TNetstringString ): raiseParseErr( result, "Invalid data: Object keys must be strings." )
+
+                value = parse_tnetstring( subkey.extra )
+                result.fields.add( (key: subkey.str, val: value) )
+
+        else:
+            raiseParseErr( result, "Invalid data: Unknown tnetstring type '$1'." % $kind )
+
+    result.extra = extra
+
+
+iterator items*( node: TNetstringNode ): TNetstringNode =
+    ## Iterator for the items of `node`. `node` has to be a TNetstringArray.
+    assert node.kind == TNetstringArray
+    for i in items( node.elems ):
+        yield i
+
+
+iterator mitems*( node: var TNetstringNode ): var TNetstringNode =
+    ## Iterator for the items of `node`. `node` has to be a TNetstringArray. Items can be
+    ## modified.
+    assert node.kind == TNetstringArray
+    for i in mitems( node.elems ):
+        yield i
+
+
+iterator pairs*( node: TNetstringNode ): tuple[ key: string, val: TNetstringNode ] =
+    ## Iterator for the child elements of `node`. `node` has to be a TNetstringObject.
+    assert node.kind == TNetstringObject
+    for key, val in items( node.fields ):
+        yield ( key, val )
+
+
+iterator mpairs*( node: var TNetstringNode ): var tuple[ key: string, val: TNetstringNode ] =
+    ## Iterator for the child elements of `node`. `node` has to be a TNetstringObject.
+    ## Items can be modified.
+    assert node.kind == TNetstringObject
+    for keyVal in mitems( node.fields ):
+        yield keyVal
+
+
+proc `$`*( node: TNetstringNode ): string =
+    ## Delegate stringification of `TNetstringNode` to its underlying object.
+    return case node.kind:
+    of TNetstringString:
+        $node.str
+    of TNetstringInt:
+        $node.num
+    of TNetstringFloat:
+        $node.fnum
+    of TNetstringBool:
+        $node.bval
+    of TNetstringNull:
+        "(nil)"
+    of TNetstringArray:
+        $node.elems
+    of TNetstringObject:
+        $node.fields
+
+
+proc `==`* ( a, b: TNetstringNode ): bool =
+    ## Check two TNetstring nodes for equality.
+    if a.isNil:
+        if b.isNil: return true
+        return false
+    elif b.isNil or a.kind != b.kind:
+        return false
+    else:
+        return case a.kind
+        of TNetstringString:
+            a.str == b.str
+        of TNetstringInt:
+            a.num == b.num
+        of TNetstringFloat:
+            a.fnum == b.fnum
+        of TNetstringBool:
+            a.bval == b.bval
+        of TNetstringNull:
+            true
+        of TNetstringArray:
+            a.elems == b.elems
+        of TNetstringObject:
+            a.fields == b.fields
+
+
+proc copy*( node: TNetstringNode ): TNetstringNode =
+    ## Perform a deep copy of TNetstringNode.
+    new( result )
+    result.kind  = node.kind
+    result.extra = node.extra
+
+    case node.kind
+    of TNetstringString:
+        result.str = node.str
+    of TNetstringInt:
+        result.num = node.num
+    of TNetstringFloat:
+        result.fnum = node.fnum
+    of TNetstringBool:
+        result.bval = node.bval
+    of TNetstringNull:
+        discard
+    of TNetstringArray:
+        result.elems = @[]
+        for item in items( node ):
+            result.elems.add( copy(item) )
+    of TNetstringObject:
+        result.fields = @[]
+        for key, value in items( node.fields ):
+            result.fields.add( (key, copy(value)) )
+
+
+proc delete*( node: TNetstringNode, key: string ) =
+    ## Deletes ``node[key]`` preserving the order of the other (key, value)-pairs.
+    assert( node.kind == TNetstringObject )
+    for i in 0..node.fields.len - 1:
+        if node.fields[i].key == key:
+            node.fields.delete( i )
+            return
+    raise newException( IndexError, "key not in object" )
+
+
+proc hash*( node: TNetstringNode ): Hash =
+    ## Compute the hash for a TNetstringString node
+    return case node.kind
+    of TNetstringString:
+        hash( node.str )
+    of TNetstringInt:
+        hash( node.num )
+    of TNetstringFloat:
+        hash( node.fnum )
+    of TNetstringBool:
+        hash( node.bval.int )
+    of TNetstringNull:
+        hash( 0 )
+    of TNetstringArray:
+        hash( node.elems )
+    of TNetstringObject:
+        hash( node.fields )
+
+
+proc len*( node: TNetstringNode ): int =
+    ## If `node` is a `TNetstringArray`, it returns the number of elements.
+    ## If `node` is a `TNetstringObject`, it returns the number of pairs.
+    ## If `node` is a `TNetstringString`, it returns strlen.
+    ## Else it returns 0.
+    return case node.kind
+    of TNetstringString:
+        node.str.len
+    of TNetstringArray:
+        node.elems.len
+    of TNetstringObject:
+        node.fields.len
+    else:
+        0
+
+
+proc `[]`*( node: TNetstringNode, name: string ): TNetstringNode =
+    ## Gets a field from a `TNetstringNode`, which must not be nil.
+    ## If the value at `name` does not exist, returns nil
+    assert( not isNil(node) )
+    assert( node.kind == TNetstringObject )
+    for key, item in node:
+        if key == name:
+            return item
+    return nil
+
+
+proc `[]`*( node: TNetstringNode, index: int ): TNetstringNode =
+    ## Gets the node at `index` in an Array. Result is undefined if `index`
+    ## is out of bounds.
+    assert( not isNil(node) )
+    assert( node.kind == TNetstringArray )
+    return node.elems[ index ]
+
+
+proc hasKey*( node: TNetstringNode, key: string ): bool =
+    ## Checks if `key` exists in `node`.
+    assert( node.kind == TNetstringObject )
+    for k, item in items( node.fields ):
+        if k == key: return true
+
+
+proc add*( parent, child: TNetstringNode ) =
+    ## Appends `child` to a TNetstringArray node `parent`.
+    assert( parent.kind == TNetstringArray )
+    parent.elems.add( child )
+
+
+proc add*( node: TNetstringNode, key: string, val: TNetstringNode ) =
+    ## Adds ``(key, val)`` pair to the TNetstringObject `node`.
+    ## For speed reasons no check for duplicate keys is performed.
+    ## (Note, ``[]=`` performs the check.)
+    assert( node.kind == TNetstringObject )
+    node.fields.add( (key, val) )
+
+
+proc `[]=`*( node: TNetstringNode, index: int, val: TNetstringNode ) =
+    ## Sets an index for a `TNetstringArray`.
+    assert( node.kind == TNetstringArray )
+    node.elems[ index ] = val
+
+
+proc `[]=`*( node: TNetstringNode, key: string, val: TNetstringNode ) =
+    ## Sets a field from a `TNetstringObject`. Performs a check for duplicate keys.
+    assert( node.kind == TNetstringObject )
+    for i in 0 .. node.fields.len - 1:
+        if node.fields[i].key == key:
+            node.fields[i].val = val
+            return
+    node.fields.add( (key, val) )
+
+
+proc dump_tnetstring*( node: TNetstringNode ): string =
+    ## Renders a TNetstring `node` as a regular string.
+    case node.kind
+    of TNetstringString:
+        result = $( node.str.len ) & ':' & node.str & ','
+    of TNetstringInt:
+        let str = $( node.num )
+        result = $( str.len ) & ':' & str & '#'
+    of TNetstringFloat:
+        let str = $( node.fnum )
+        result = $( str.len ) & ':' & str & '^'
+    of TNetstringBool:
+        result = if node.bval: "4:true!" else: "5:false!"
+    of TNetstringNull:
+        result = "0:~"
+    of TNetstringArray:
+        result = ""
+        for n in node.items:
+            result = result & n.dump_tnetstring
+        result = $( result.len ) & ':' & result & ']'
+    of TNetstringObject:
+        result = ""
+        for key, val in node.pairs:
+            result = result & $( key.len ) & ':' & key & ',' # key
+            result = result & val.dump_tnetstring            # val
+        result = $( result.len ) & ':' & result & '}'
+
+
+#
+# Tests!
+#
+when isMainModule:
+
+    # Expected exceptions
+    #
+    try:
+        discard parse_tnetstring( "totally invalid" )
+    except TNetstringParseError:
+        doAssert( true, "invalid tnetstring" )
+    try:
+        discard parse_tnetstring( "what:ever" )
+    except TNetstringParseError:
+        doAssert( true, "bad length" )
+    try:
+        discard parse_tnetstring( "3:yep~" )
+    except TNetstringParseError:
+        doAssert( true, "null w/ > 0 length" )
+    try:
+        discard parse_tnetstring( "8:1:1#1:1#}" )
+    except TNetstringParseError:
+        doAssert( true, "hash with non-string key" )
+    try:
+        discard parse_tnetstring( "7:4:test,}" )
+    except TNetstringParseError:
+        doAssert( true, "hash with odd number of elements" )
+    try:
+        discard parse_tnetstring( "2:25*" )
+    except TNetstringParseError:
+        doAssert( true, "unknown netstring tag" )
+
+    # Equality
+    #
+    let tnet_int = parse_tnetstring( "1:1#" )
+    doAssert( tnet_int == tnet_int )
+    doAssert( tnet_int == parse_tnetstring( "1:1#" ) )
+    doAssert( parse_tnetstring( "0:~" ) == parse_tnetstring( "0:~" ) )
+
+    # Type detection
+    #
+    doAssert( tnet_int.kind == TNetstringInt )
+    doAssert( parse_tnetstring( "1:a," ).kind         == TNetstringString )
+    doAssert( parse_tnetstring( "3:1.0^" ).kind       == TNetstringFloat )
+    doAssert( parse_tnetstring( "5:false!" ).kind     == TNetstringBool )
+    doAssert( parse_tnetstring( "0:~" ).kind          == TNetstringNull )
+    doAssert( parse_tnetstring( "9:2:hi,1:1#}" ).kind == TNetstringObject )
+    doAssert( parse_tnetstring( "8:1:1#1:2#]" ).kind  == TNetstringArray )
+
+    # Iteration (both array and tuple)
+    #
+    var
+        keys: array[ 2, string ]
+        vals: array[ 4, string ]
+        k_idx = 0
+        idx = 0
+    for key, val in parse_tnetstring( "35:2:hi,8:1:a,1:b,]5:there,8:1:c,1:d,]}" ):
+        keys[ idx ] = key
+        idx = idx + 1
+        for item in val:
+            vals[ k_idx ] = item.str
+            k_idx = k_idx + 1
+    doAssert( keys == ["hi","there"] )
+    doassert( vals == ["a","b","c","d"] )
+
+    # Deep copies
+    #
+    var original = parse_tnetstring( "35:2:hi,8:1:a,1:b,]5:there,8:1:c,1:d,]}" )
+    var copied   = original.copy
+    doAssert( original == copied )
+    doAssert( original.repr != copied.repr )
+    doAssert( original.fields.pop.val.elems.pop.repr != copied.fields.pop.val.elems.pop.repr )
+
+    # Key deletion
+    #
+    var tnet_obj = parse_tnetstring( "35:2:hi,8:1:a,1:b,]5:there,8:1:c,1:d,]}" )
+    tnet_obj.delete( "hi" )
+    doAssert( tnet_obj.fields.len == 1 )
+
+    # Hashing
+    #
+    doAssert( tnet_int.hash == 1.hash )
+    doAssert( parse_tnetstring( "4:true!" ).hash == hash( true.int ) )
+
+    # Length checks.
+    #
+    tnet_obj = parse_tnetstring( "35:2:hi,8:1:a,1:b,]5:there,8:1:c,1:d,]}" )
+    doAssert( parse_tnetstring( "0:~" ).len == 0 )
+    doAssert( tnet_obj.len == 2 )
+    doAssert( parse_tnetstring( "8:1:1#1:2#]" ).len == 2 )
+    doAssert( parse_tnetstring( "5:hallo," ).len == 5 )
+
+    # Index accessors
+    #
+    tnet_obj = parse_tnetstring( "20:1:1#1:2#1:3#1:4#1:5#]" )
+    doAssert( tnet_obj[ 2 ].num == 3 )
+
+    # Key accessors
+    #
+    tnet_obj = parse_tnetstring( "11:2:hi,3:yep,}" )
+    doAssert( $tnet_obj["hi"] == "yep" )
+    doAssert( tnet_obj.has_key( "hi" ) == true )
+    doAssert( tnet_obj.has_key( "nope-not-here" ) == false )
+
+    # Adding elements to an existing TNetstring array
+    #
+    var tnet_array = newTNetstringArray()
+    for i in 1 .. 10:
+        tnet_obj = newTNetstringInt( i )
+        tnet_array.add( tnet_obj )
+    tnet_array[ 6 ] = newTNetstringString( "yep" )
+    doAssert( tnet_array.len == 10 )
+    doAssert( tnet_array[ 4 ].num == 5 )
+    doAssert( tnet_array[ 6 ].str == "yep" )
+
+    # Adding pairs to an existing TNetstring aobject.
+    #
+    tnet_obj = newTNetstringObject()
+    tnet_obj.add( "yo", newTNetstringInt(1) )
+    tnet_obj.add( "yep", newTNetstringInt(2) )
+    doAssert( tnet_obj["yo"].num == 1 )
+    doAssert( tnet_obj["yep"].num == 2 )
+    doAssert( tnet_obj.len == 2 )
+    tnet_obj[ "more" ] = newTNetstringInt(1)
+    tnet_obj[ "yo" ] = newTNetstringInt(1) # dup check
+    doAssert( tnet_obj.len == 3 )
+
+    # Serialization.
+    #
+    var tstr = "308:9:givenName,6:Mahlon,16:departmentNumber,22:Information Technology," &
+        "5:title,19:Senior Technologist,13:accountConfig,48:7:vmemail,4:true!7:allpage," &
+        "5:false!7:galhide,0:~}13:homeDirectory,14:/home/m/mahlon,3:uid,6:mahlon,9:yubi" &
+        "KeyId,12:vvidhghkhehj,5:gecos,12:Mahlon Smith,2:sn,5:Smith,14:employeeNumber,5:12921#}"
+    tnet_obj = parse_tnetstring( tstr )
+    doAssert( tstr == tnet_obj.dump_tnetstring )
+
+    # Value fetching methods
+    #
+    var tnet_null = newTNetstringNull()
+    tnet_obj = newTNetstringString( "Hello." )
+    doAssert( tnet_obj.getStr == "Hello." )
+    doAssert( tnet_null.getStr("nope") == "nope" )
+    doAssert( tnet_null.getStr == "" )
+    tnet_obj = newTNetstringInt( 42 )
+    doAssert( tnet_obj.getInt == 42 )
+    doAssert( tnet_null.getInt == 0 )
+    doAssert( tnet_null.getInt(1) == 1 )
+    tnet_obj = newTNetstringFloat( 1.0 )
+    doAssert( tnet_obj.getFloat == 1.0 )
+    doAssert( tnet_null.getFloat == 0 )
+    doAssert( tnet_null.getFloat(0.1) == 0.1 )
+    tnet_obj = newTNetstringObject()
+    tnet_obj[ "yay" ] = newTNetstringInt( 1 )
+    doAssert( tnet_obj.getFields[0].val == newTNetstringInt(1) )
+    doAssert( tnet_null.getFields.len == 0 )
+    tnet_obj = newTNetstringArray()
+    tnet_obj.add( newTNetstringInt(1) )
+    doAssert( tnet_obj.getElems[0] == newTNetstringInt(1) )
+    doAssert( tnet_null.getElems.len == 0 )
+
+
+    echo "* Tests passed!"
+
+    while true and defined( testing ):
+        for line in readline( stdin ).split_lines:
+            let input = line.strip
+            try:
+                var tnetstring = parse_tnetstring( input )
+                echo "  parsed     --> ", tnetstring
+                echo "  serialized --> ", tnetstring.dump_tnetstring, "\n"
+            except TNetstringParseError:
+                echo input, " --> ", getCurrentExceptionMsg()
+
--- a/tnetstring.nim	Sun Apr 30 04:38:52 2017 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,713 +0,0 @@
-#
-# Copyright (c) 2015, Mahlon E. Smith <mahlon@martini.nu>
-# All rights reserved.
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-#     * Redistributions of source code must retain the above copyright
-#       notice, this list of conditions and the following disclaimer.
-#
-#     * Redistributions in binary form must reproduce the above copyright
-#       notice, this list of conditions and the following disclaimer in the
-#       documentation and/or other materials provided with the distribution.
-#
-#     * Neither the name of Mahlon E. Smith nor the names of his
-#       contributors may be used to endorse or promote products derived
-#       from this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
-# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
-# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-## This module implements a simple TNetstring parser and serializer.
-## TNetString stands for "tagged netstring" and is a modification of Dan
-## Bernstein's netstrings specification.  TNetstrings allow for the same data
-## structures as JSON but in a format that is resistant to buffer overflows
-## and backward compatible with original netstrings.  They make no assumptions
-## about string contents, allowing for easy transmission of binary data mixed
-## with strongly typed values.
-
-## See http://cr.yp.to/proto/netstrings.txt and http://tnetstrings.org/ for additional information.
-##
-## This module borrows heavily (in both usage and code) from the nim JSON stdlib
-## (json.nim) -- (c) Copyright 2015 Andreas Rumpf, Dominik Picheta.
-## 
-## Usage example:
-##
-## .. code-block:: nim
-##
-##   let
-##       tnetstr = "52:4:test,3:1.3^4:key2,4:true!6:things,12:1:1#1:2#1:3#]}"
-##       tnetobj = parse_tnetstring( tnetstr )
-##
-##   # tnetobj is now equivalent to the structure:
-##   # @[(key: test, val: 1.3), (key: key2, val: true), (key: things, val: @[1, 2, 3])]
-##
-##   assert( tnetobj.kind == TNetstringObject )
-##   echo tnetobj[ "test" ]
-##   echo tnetobj[ "key2" ]
-##   for item in tnetobj[ "things" ]:
-##       echo item
-##   
-## Results in:
-##
-## .. code-block:: nim
-##
-##   1.3
-##   true
-##   1
-##   2
-##   3
-##
-## This module can also be used to reasonably create a serialized
-## TNetstring, suitable for network transmission:
-##
-## .. code-block:: nim
-##    
-##    let
-##        number  = 1000
-##        list    = @[ "thing1", "thing2" ]
-##        tnettop = newTNetstringArray() # top-level array
-##        tnetsub = newTNetstringArray() # sub array
-##    
-##    tnettop.add( newTNetstringInt(number) )
-##    for item in list:
-##        tnetsub.add( newTNetstringString(item) )
-##    tnettop.add( tnetsub )
-##    
-##    # Equivalent to: @[1000, @[thing1, thing2]]
-##    echo dump_tnetstring( tnettop )
-##
-## Results in:
-##
-## .. code-block:: nim
-##    
-##    29:4:1000#18:6:thing1,6:thing2,]]
-##
-
-import
-    hashes,
-    parseutils,
-    strutils
-
-const version = "0.1.1"
-
-type 
-  TNetstringKind* = enum     ## enumeration of all valid types
-    TNetstringString,        ## a string literal
-    TNetstringInt,           ## an integer literal
-    TNetstringFloat,         ## a float literal
-    TNetstringBool,          ## a ``true`` or ``false`` value
-    TNetstringNull,          ## the value ``null``
-    TNetstringObject,        ## an object: the ``}`` token
-    TNetstringArray          ## an array: the ``]`` token
-
-  TNetstringNode* = ref TNetstringNodeObj
-  TNetstringNodeObj* {.acyclic.} = object
-      extra*: string
-      case kind*: TNetstringKind
-      of TNetstringString:
-          str*: string
-      of TNetstringInt:
-          num*: BiggestInt
-      of TNetstringFloat:
-          fnum*: float
-      of TNetstringBool:
-          bval*: bool
-      of TNetstringNull:
-          nil
-      of TNetstringObject:
-          fields*: seq[ tuple[key: string, val: TNetstringNode] ]
-      of TNetstringArray:
-          elems*: seq[ TNetstringNode ]
-
-  TNetstringParseError* = object of ValueError ## Raised for a TNetstring error
-
-
-proc raiseParseErr*( t: TNetstringNode, msg: string ) {.noinline, noreturn.} =
-  ## Raises a `TNetstringParseError` exception.
-  raise newException( TNetstringParseError, msg )
-
-
-proc newTNetstringString*( s: string ): TNetstringNode =
-    ## Create a new String typed TNetstringNode.
-    new( result )
-    result.kind = TNetstringString
-    result.str = s
-
-
-proc newTNetstringInt*( i: BiggestInt ): TNetstringNode =
-    ## Create a new Integer typed TNetstringNode.
-    new( result )
-    result.kind = TNetstringInt
-    result.num = i
-
-
-proc newTNetstringFloat*( f: float ): TNetstringNode =
-    ## Create a new Float typed TNetstringNode.
-    new( result )
-    result.kind = TNetstringFloat
-    result.fnum = f
-
-
-proc newTNetstringBool*( b: bool ): TNetstringNode =
-    ## Create a new Boolean typed TNetstringNode.
-    new( result )
-    result.kind = TNetstringBool
-    result.bval = b
-
-
-proc newTNetstringNull*(): TNetstringNode =
-    ## Create a new nil typed TNetstringNode.
-    new( result )
-    result.kind = TNetstringNull
-
-
-proc newTNetstringObject*(): TNetstringNode =
-    ## Create a new Object typed TNetstringNode.
-    new( result )
-    result.kind = TNetstringObject
-    result.fields = @[]
-
-
-proc newTNetstringArray*(): TNetstringNode =
-    ## Create a new Array typed TNetstringNode.
-    new( result )
-    result.kind = TNetstringArray
-    result.elems = @[]
-
-
-proc getStr*( node: TNetstringNode, default: string = "" ): string =
-    ## Retrieves the string value of a `TNetstringString TNetstringNodee`.
-    ## Returns ``default`` if ``node`` is not a ``TNetstringString``.
-    if node.kind != TNetstringString: return default
-    return node.str
-
-
-proc getInt*( node: TNetstringNode, default: BiggestInt = 0 ): BiggestInt =
-    ## Retrieves the int value of a `TNetstringInt TNetstringNode`.
-    ## Returns ``default`` if ``node`` is not a ``TNetstringInt``.
-    if node.kind != TNetstringInt: return default
-    return node.num
-
-
-proc getFloat*( node: TNetstringNode, default: float = 0.0 ): float =
-    ## Retrieves the float value of a `TNetstringFloat TNetstringNode`.
-    ## Returns ``default`` if ``node`` is not a ``TNetstringFloat``.
-    if node.kind != TNetstringFloat: return default
-    return node.fnum
-
-
-proc getBool*( node: TNetstringNode, default: bool = false ): bool =
-    ## Retrieves the bool value of a `TNetstringBool TNetstringNode`.
-    ## Returns ``default`` if ``node`` is not a ``TNetstringBool``.
-    if node.kind != TNetstringBool: return default
-    return node.bval
-
-
-proc getFields*( node: TNetstringNode,
-    default: seq[tuple[key: string, val: TNetstringNode]] = @[] ):
-        seq[tuple[key: string, val: TNetstringNode]] =
-    ## Retrieves the key, value pairs of a `TNetstringObject TNetstringNode`.
-    ## Returns ``default`` if ``node`` is not a ``TNetstringObject``.
-    if node.kind != TNetstringObject: return default
-    return node.fields
-
-
-proc getElems*( node: TNetstringNode, default: seq[TNetstringNode] = @[] ): seq[TNetstringNode] =
-    ## Retrieves the values of a `TNetstringArray TNetstringNode`.
-    ## Returns ``default`` if ``node`` is not a ``TNetstringArray``.
-    if node.kind != TNetstringArray: return default
-    return node.elems
-
-
-proc parse_tnetstring*( data: string ): TNetstringNode =
-    ## Given an encoded tnetstring, parse and return a TNetstringNode.
-    var
-        length:  int
-        kind:    char
-        payload: string
-        extra:   string
-
-    let sep_pos = data.skipUntil( ':' )
-    if sep_pos == data.len: raiseParseErr( result, "Invalid data: No separator token found." )
-
-    try:
-        length       = data[ 0 .. sep_pos - 1 ].parseInt
-        kind         = data[ sep_pos + length + 1 ]
-        payload      = data[ sep_pos + 1 .. sep_pos + length ]
-        extra        = data[ sep_pos + length + 2 .. ^1 ]
-
-    except ValueError, IndexError:
-        var msg = getCurrentExceptionMsg()
-        raiseParseErr( result, msg )
-
-    case kind:
-        of ',':
-            result = newTNetstringString( payload )
-
-        of '#':
-            try:
-                result = newTNetstringInt( payload.parseBiggestInt )
-            except ValueError:
-                var msg = getCurrentExceptionMsg()
-                raiseParseErr( result, msg )
-
-        of '^':
-            try:
-                result = newTNetstringFloat( payload.parseFloat )
-            except ValueError:
-                var msg = getCurrentExceptionMsg()
-                raiseParseErr( result, msg )
-
-        of '!':
-            result = newTNetstringBool( payload == "true" )
-
-        of '~':
-            if length != 0: raiseParseErr( result, "Invalid data: Payload must be 0 length for null." )
-            result = newTNetstringNull()
-            
-        of ']':
-            result = newTNetstringArray()
-
-            var subnode = parse_tnetstring( payload )
-            result.elems.add( subnode )
-
-            while subnode.extra != "":
-                subnode = parse_tnetstring( subnode.extra )
-                result.elems.add( subnode )
-
-        of '}':
-            result = newTNetstringObject()
-            var key = parse_tnetstring( payload )
-
-            if ( key.extra == "" ): raiseParseErr( result, "Invalid data: Unbalanced tuple." )
-            if ( key.kind != TNetstringString ): raiseParseErr( result, "Invalid data: Object keys must be strings." )
-
-            var value = parse_tnetstring( key.extra )
-            result.fields.add( (key: key.str, val: value) )
-
-            while value.extra != "":
-                var subkey = parse_tnetstring( value.extra )
-                if ( subkey.extra == "" ): raiseParseErr( result, "Invalid data: Unbalanced tuple." )
-                if ( subkey.kind != TNetstringString ): raiseParseErr( result, "Invalid data: Object keys must be strings." )
-
-                value = parse_tnetstring( subkey.extra )
-                result.fields.add( (key: subkey.str, val: value) )
-
-        else:
-            raiseParseErr( result, "Invalid data: Unknown tnetstring type '$1'." % $kind )
-
-    result.extra = extra
-
-
-iterator items*( node: TNetstringNode ): TNetstringNode =
-    ## Iterator for the items of `node`. `node` has to be a TNetstringArray.
-    assert node.kind == TNetstringArray
-    for i in items( node.elems ):
-        yield i
-
-
-iterator mitems*( node: var TNetstringNode ): var TNetstringNode =
-    ## Iterator for the items of `node`. `node` has to be a TNetstringArray. Items can be
-    ## modified.
-    assert node.kind == TNetstringArray
-    for i in mitems( node.elems ):
-        yield i
-
-
-iterator pairs*( node: TNetstringNode ): tuple[ key: string, val: TNetstringNode ] =
-    ## Iterator for the child elements of `node`. `node` has to be a TNetstringObject.
-    assert node.kind == TNetstringObject
-    for key, val in items( node.fields ):
-        yield ( key, val )
-
-
-iterator mpairs*( node: var TNetstringNode ): var tuple[ key: string, val: TNetstringNode ] =
-    ## Iterator for the child elements of `node`. `node` has to be a TNetstringObject.
-    ## Items can be modified.
-    assert node.kind == TNetstringObject
-    for keyVal in mitems( node.fields ):
-        yield keyVal
-
-
-proc `$`*( node: TNetstringNode ): string =
-    ## Delegate stringification of `TNetstringNode` to its underlying object.
-    return case node.kind:
-    of TNetstringString:
-        $node.str
-    of TNetstringInt:
-        $node.num
-    of TNetstringFloat:
-        $node.fnum
-    of TNetstringBool:
-        $node.bval
-    of TNetstringNull:
-        "(nil)"
-    of TNetstringArray:
-        $node.elems
-    of TNetstringObject:
-        $node.fields
-
-
-proc `==`* ( a, b: TNetstringNode ): bool =
-    ## Check two TNetstring nodes for equality.
-    if a.isNil:
-        if b.isNil: return true
-        return false
-    elif b.isNil or a.kind != b.kind:
-        return false
-    else:
-        return case a.kind
-        of TNetstringString:
-            a.str == b.str
-        of TNetstringInt:
-            a.num == b.num
-        of TNetstringFloat:
-            a.fnum == b.fnum
-        of TNetstringBool:
-            a.bval == b.bval
-        of TNetstringNull:
-            true
-        of TNetstringArray:
-            a.elems == b.elems
-        of TNetstringObject:
-            a.fields == b.fields
-
-
-proc copy*( node: TNetstringNode ): TNetstringNode =
-    ## Perform a deep copy of TNetstringNode.
-    new( result )
-    result.kind  = node.kind
-    result.extra = node.extra
-
-    case node.kind
-    of TNetstringString:
-        result.str = node.str
-    of TNetstringInt:
-        result.num = node.num
-    of TNetstringFloat:
-        result.fnum = node.fnum
-    of TNetstringBool:
-        result.bval = node.bval
-    of TNetstringNull:
-        discard
-    of TNetstringArray:
-        result.elems = @[]
-        for item in items( node ):
-            result.elems.add( copy(item) )
-    of TNetstringObject:
-        result.fields = @[]
-        for key, value in items( node.fields ):
-            result.fields.add( (key, copy(value)) )
-
-
-proc delete*( node: TNetstringNode, key: string ) =
-    ## Deletes ``node[key]`` preserving the order of the other (key, value)-pairs.
-    assert( node.kind == TNetstringObject )
-    for i in 0..node.fields.len - 1:
-        if node.fields[i].key == key:
-            node.fields.delete( i )
-            return
-    raise newException( IndexError, "key not in object" )
-
-
-proc hash*( node: TNetstringNode ): Hash =
-    ## Compute the hash for a TNetstringString node
-    return case node.kind
-    of TNetstringString:
-        hash( node.str )
-    of TNetstringInt:
-        hash( node.num )
-    of TNetstringFloat:
-        hash( node.fnum )
-    of TNetstringBool:
-        hash( node.bval.int )
-    of TNetstringNull:
-        hash( 0 )
-    of TNetstringArray:
-        hash( node.elems )
-    of TNetstringObject:
-        hash( node.fields )
-
-
-proc len*( node: TNetstringNode ): int =
-    ## If `node` is a `TNetstringArray`, it returns the number of elements.
-    ## If `node` is a `TNetstringObject`, it returns the number of pairs.
-    ## If `node` is a `TNetstringString`, it returns strlen.
-    ## Else it returns 0.
-    return case node.kind
-    of TNetstringString:
-        node.str.len
-    of TNetstringArray:
-        node.elems.len
-    of TNetstringObject:
-        node.fields.len
-    else:
-        0
-
-
-proc `[]`*( node: TNetstringNode, name: string ): TNetstringNode =
-    ## Gets a field from a `TNetstringNode`, which must not be nil.
-    ## If the value at `name` does not exist, returns nil
-    assert( not isNil(node) )
-    assert( node.kind == TNetstringObject )
-    for key, item in node:
-        if key == name:
-            return item
-    return nil
-
-
-proc `[]`*( node: TNetstringNode, index: int ): TNetstringNode =
-    ## Gets the node at `index` in an Array. Result is undefined if `index`
-    ## is out of bounds.
-    assert( not isNil(node) )
-    assert( node.kind == TNetstringArray )
-    return node.elems[ index ]
-
-
-proc hasKey*( node: TNetstringNode, key: string ): bool =
-    ## Checks if `key` exists in `node`.
-    assert( node.kind == TNetstringObject )
-    for k, item in items( node.fields ):
-        if k == key: return true
-
-
-proc add*( parent, child: TNetstringNode ) =
-    ## Appends `child` to a TNetstringArray node `parent`.
-    assert( parent.kind == TNetstringArray )
-    parent.elems.add( child )
-
-
-proc add*( node: TNetstringNode, key: string, val: TNetstringNode ) =
-    ## Adds ``(key, val)`` pair to the TNetstringObject `node`.
-    ## For speed reasons no check for duplicate keys is performed.
-    ## (Note, ``[]=`` performs the check.)
-    assert( node.kind == TNetstringObject )
-    node.fields.add( (key, val) )
-
-
-proc `[]=`*( node: TNetstringNode, index: int, val: TNetstringNode ) =
-    ## Sets an index for a `TNetstringArray`.
-    assert( node.kind == TNetstringArray )
-    node.elems[ index ] = val
-
-
-proc `[]=`*( node: TNetstringNode, key: string, val: TNetstringNode ) =
-    ## Sets a field from a `TNetstringObject`. Performs a check for duplicate keys.
-    assert( node.kind == TNetstringObject )
-    for i in 0 .. node.fields.len - 1:
-        if node.fields[i].key == key:
-            node.fields[i].val = val
-            return
-    node.fields.add( (key, val) )
-
-
-proc dump_tnetstring*( node: TNetstringNode ): string =
-    ## Renders a TNetstring `node` as a regular string.
-    case node.kind
-    of TNetstringString:
-        result = $( node.str.len ) & ':' & node.str & ','
-    of TNetstringInt:
-        let str = $( node.num )
-        result = $( str.len ) & ':' & str & '#'
-    of TNetstringFloat:
-        let str = $( node.fnum )
-        result = $( str.len ) & ':' & str & '^'
-    of TNetstringBool:
-        result = if node.bval: "4:true!" else: "5:false!"
-    of TNetstringNull:
-        result = "0:~"
-    of TNetstringArray:
-        result = ""
-        for n in node.items:
-            result = result & n.dump_tnetstring
-        result = $( result.len ) & ':' & result & ']'
-    of TNetstringObject:
-        result = ""
-        for key, val in node.pairs:
-            result = result & $( key.len ) & ':' & key & ',' # key
-            result = result & val.dump_tnetstring            # val
-        result = $( result.len ) & ':' & result & '}'
-
-
-#
-# Tests!
-#
-when isMainModule:
-
-    # Expected exceptions
-    #
-    try:
-        discard parse_tnetstring( "totally invalid" )
-    except TNetstringParseError:
-        doAssert( true, "invalid tnetstring" )
-    try:
-        discard parse_tnetstring( "what:ever" )
-    except TNetstringParseError:
-        doAssert( true, "bad length" )
-    try:
-        discard parse_tnetstring( "3:yep~" )
-    except TNetstringParseError:
-        doAssert( true, "null w/ > 0 length" )
-    try:
-        discard parse_tnetstring( "8:1:1#1:1#}" )
-    except TNetstringParseError:
-        doAssert( true, "hash with non-string key" )
-    try:
-        discard parse_tnetstring( "7:4:test,}" )
-    except TNetstringParseError:
-        doAssert( true, "hash with odd number of elements" )
-    try:
-        discard parse_tnetstring( "2:25*" )
-    except TNetstringParseError:
-        doAssert( true, "unknown netstring tag" )
-
-    # Equality
-    #
-    let tnet_int = parse_tnetstring( "1:1#" )
-    doAssert( tnet_int == tnet_int )
-    doAssert( tnet_int == parse_tnetstring( "1:1#" ) )
-    doAssert( parse_tnetstring( "0:~" ) == parse_tnetstring( "0:~" ) )
-
-    # Type detection
-    #
-    doAssert( tnet_int.kind == TNetstringInt )
-    doAssert( parse_tnetstring( "1:a," ).kind         == TNetstringString )
-    doAssert( parse_tnetstring( "3:1.0^" ).kind       == TNetstringFloat )
-    doAssert( parse_tnetstring( "5:false!" ).kind     == TNetstringBool )
-    doAssert( parse_tnetstring( "0:~" ).kind          == TNetstringNull )
-    doAssert( parse_tnetstring( "9:2:hi,1:1#}" ).kind == TNetstringObject )
-    doAssert( parse_tnetstring( "8:1:1#1:2#]" ).kind  == TNetstringArray )
-
-    # Iteration (both array and tuple)
-    #
-    var
-        keys: array[ 2, string ]
-        vals: array[ 4, string ]
-        k_idx = 0
-        idx = 0
-    for key, val in parse_tnetstring( "35:2:hi,8:1:a,1:b,]5:there,8:1:c,1:d,]}" ):
-        keys[ idx ] = key
-        idx = idx + 1
-        for item in val:
-            vals[ k_idx ] = item.str
-            k_idx = k_idx + 1
-    doAssert( keys == ["hi","there"] )
-    doassert( vals == ["a","b","c","d"] )
-
-    # Deep copies
-    #
-    var original = parse_tnetstring( "35:2:hi,8:1:a,1:b,]5:there,8:1:c,1:d,]}" )
-    var copied   = original.copy
-    doAssert( original == copied )
-    doAssert( original.repr != copied.repr )
-    doAssert( original.fields.pop.val.elems.pop.repr != copied.fields.pop.val.elems.pop.repr )
-
-    # Key deletion
-    #
-    var tnet_obj = parse_tnetstring( "35:2:hi,8:1:a,1:b,]5:there,8:1:c,1:d,]}" )
-    tnet_obj.delete( "hi" )
-    doAssert( tnet_obj.fields.len == 1 )
-
-    # Hashing
-    #
-    doAssert( tnet_int.hash == 1.hash )
-    doAssert( parse_tnetstring( "4:true!" ).hash == hash( true.int ) )
-
-    # Length checks.
-    #
-    tnet_obj = parse_tnetstring( "35:2:hi,8:1:a,1:b,]5:there,8:1:c,1:d,]}" )
-    doAssert( parse_tnetstring( "0:~" ).len == 0 )
-    doAssert( tnet_obj.len == 2 )
-    doAssert( parse_tnetstring( "8:1:1#1:2#]" ).len == 2 )
-    doAssert( parse_tnetstring( "5:hallo," ).len == 5 )
-
-    # Index accessors
-    #
-    tnet_obj = parse_tnetstring( "20:1:1#1:2#1:3#1:4#1:5#]" )
-    doAssert( tnet_obj[ 2 ].num == 3 )
-
-    # Key accessors
-    #
-    tnet_obj = parse_tnetstring( "11:2:hi,3:yep,}" )
-    doAssert( $tnet_obj["hi"] == "yep" )
-    doAssert( tnet_obj.has_key( "hi" ) == true )
-    doAssert( tnet_obj.has_key( "nope-not-here" ) == false )
-
-    # Adding elements to an existing TNetstring array
-    #
-    var tnet_array = newTNetstringArray()
-    for i in 1 .. 10:
-        tnet_obj = newTNetstringInt( i )
-        tnet_array.add( tnet_obj )
-    tnet_array[ 6 ] = newTNetstringString( "yep" )
-    doAssert( tnet_array.len == 10 )
-    doAssert( tnet_array[ 4 ].num == 5 )
-    doAssert( tnet_array[ 6 ].str == "yep" )
-
-    # Adding pairs to an existing TNetstring aobject.
-    #
-    tnet_obj = newTNetstringObject()
-    tnet_obj.add( "yo", newTNetstringInt(1) )
-    tnet_obj.add( "yep", newTNetstringInt(2) )
-    doAssert( tnet_obj["yo"].num == 1 )
-    doAssert( tnet_obj["yep"].num == 2 )
-    doAssert( tnet_obj.len == 2 )
-    tnet_obj[ "more" ] = newTNetstringInt(1)
-    tnet_obj[ "yo" ] = newTNetstringInt(1) # dup check
-    doAssert( tnet_obj.len == 3 )
-
-    # Serialization.
-    #
-    var tstr = "308:9:givenName,6:Mahlon,16:departmentNumber,22:Information Technology," &
-        "5:title,19:Senior Technologist,13:accountConfig,48:7:vmemail,4:true!7:allpage," &
-        "5:false!7:galhide,0:~}13:homeDirectory,14:/home/m/mahlon,3:uid,6:mahlon,9:yubi" &
-        "KeyId,12:vvidhghkhehj,5:gecos,12:Mahlon Smith,2:sn,5:Smith,14:employeeNumber,5:12921#}"
-    tnet_obj = parse_tnetstring( tstr )
-    doAssert( tstr == tnet_obj.dump_tnetstring )
-
-    # Value fetching methods
-    #
-    var tnet_null = newTNetstringNull()
-    tnet_obj = newTNetstringString( "Hello." )
-    doAssert( tnet_obj.getStr == "Hello." )
-    doAssert( tnet_null.getStr("nope") == "nope" )
-    doAssert( tnet_null.getStr == "" )
-    tnet_obj = newTNetstringInt( 42 )
-    doAssert( tnet_obj.getInt == 42 )
-    doAssert( tnet_null.getInt == 0 )
-    doAssert( tnet_null.getInt(1) == 1 )
-    tnet_obj = newTNetstringFloat( 1.0 )
-    doAssert( tnet_obj.getFloat == 1.0 )
-    doAssert( tnet_null.getFloat == 0 )
-    doAssert( tnet_null.getFloat(0.1) == 0.1 )
-    tnet_obj = newTNetstringObject()
-    tnet_obj[ "yay" ] = newTNetstringInt( 1 )
-    doAssert( tnet_obj.getFields[0].val == newTNetstringInt(1) )
-    doAssert( tnet_null.getFields.len == 0 )
-    tnet_obj = newTNetstringArray()
-    tnet_obj.add( newTNetstringInt(1) )
-    doAssert( tnet_obj.getElems[0] == newTNetstringInt(1) )
-    doAssert( tnet_null.getElems.len == 0 )
-
-
-    echo "* Tests passed!"
-
-    while true and defined( testing ):
-        for line in readline( stdin ).split_lines:
-            let input = line.strip
-            try:
-                var tnetstring = parse_tnetstring( input )
-                echo "  parsed     --> ", tnetstring
-                echo "  serialized --> ", tnetstring.dump_tnetstring, "\n"
-            except TNetstringParseError:
-                echo input, " --> ", getCurrentExceptionMsg()
-
--- a/tnetstring.nimble	Sun Apr 30 04:38:52 2017 +0000
+++ b/tnetstring.nimble	Mon Oct 08 10:19:07 2018 -0700
@@ -1,10 +1,17 @@
-[Package]
-name          = "tnetstring"
-version       = "0.1.1"
+
+# Package
+
+version       = "0.1.2"
 author        = "Mahlon E. Smith <mahlon@martini.nu>"
+description   = "A new awesome nimble package"
 description   = "Parsing and serializing for the TNetstring format."
 license       = "MIT"
+installExt    = @["nim"]
+bin           = @["tnetstring"]
+srcDir        = "src"
 
-[Deps]
-Requires: "nim >= 0.11.0"
+
+# Dependencies
 
+requires "nim >= 0.19.0"
+