From 5fbcf93860e68d7ddde4b01aa4b7222a7fcaaabc Mon Sep 17 00:00:00 2001 From: Charles Blake Date: Thu, 12 Feb 2015 05:22:04 -0500 Subject: Add hcode,rightSize,rawGetKnownHC. Fix inf loop. Make similar changes to those made in sets.nim, including hcode, rightSize rawGet/rawGetKnownHC result protocol, nextTry probe sequence to be the cache friendlier h=h+1 which in turn allows supporting changing deletion to fix the infinite loop bug with local rehashing which in turn has desirable properties of graceful table aging when deletes do happen and also making insert-only usage patterns no longer pay any time/space cost to check deleted status. Unlike collections.sets, this module has add() for duplicate key inserts and a 3rd type of table, CountTable. The first wrinkle is handled by introducing a rawGetDeep for unconditionally adding entries along collision chains. This point of CountTable seems to be space efficiency at 2 items per slot. These changes retain that by keeping the val==0 => EMPTY rule and not caching hash codes. putImpl is expanded in-place for CountTable since the new putImpl() is too different. { Depending on table size relative to caches & key expense, regular Table[A,B] may become faster than CountTable, especially if the basic count update could be something like inc(mGetOrPut(t, key, 0)). } Unit tests pass, but in this module those are much more of just a demo than probing for bugs. Should exercise/test this a little more before merging. --- lib/pure/collections/tables.nim | 200 ++++++++++++++++++++++++++++------------ 1 file changed, 140 insertions(+), 60 deletions(-) diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 9dcc97148..da9d21050 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -71,8 +71,7 @@ import {.pragma: myShallow.} type - SlotEnum = enum seEmpty, seFilled, seDeleted - KeyValuePair[A, B] = tuple[slot: SlotEnum, key: A, val: B] + KeyValuePair[A, B] = tuple[hcode: THash, key: A, val: B] KeyValuePairSeq[A, B] = seq[KeyValuePair[A, B]] Table* {.myShallow.}[A, B] = object ## generic hash table data: KeyValuePairSeq[A, B] @@ -84,6 +83,14 @@ type when not defined(nimhygiene): {.pragma: dirty.} +# hcode for real keys cannot be zero. hcode==0 signifies an empty slot. These +# two procs retain clarity of that encoding without the space cost of an enum. +proc isEmpty(hcode: THash): bool {.inline.} = + result = hcode == 0 + +proc isFilled(hcode: THash): bool {.inline.} = + result = hcode != 0 + proc len*[A, B](t: Table[A, B]): int = ## returns the number of keys in `t`. result = t.counter @@ -91,28 +98,28 @@ proc len*[A, B](t: Table[A, B]): int = iterator pairs*[A, B](t: Table[A, B]): tuple[key: A, val: B] = ## iterates over any (key, value) pair in the table `t`. for h in 0..high(t.data): - if t.data[h].slot == seFilled: yield (t.data[h].key, t.data[h].val) + if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) iterator mpairs*[A, B](t: var Table[A, B]): tuple[key: A, val: var B] = ## iterates over any (key, value) pair in the table `t`. The values ## can be modified. for h in 0..high(t.data): - if t.data[h].slot == seFilled: yield (t.data[h].key, t.data[h].val) + if isFilled(t.data[h].slot): yield (t.data[h].key, t.data[h].val) iterator keys*[A, B](t: Table[A, B]): A = ## iterates over any key in the table `t`. for h in 0..high(t.data): - if t.data[h].slot == seFilled: yield t.data[h].key + if isFilled(t.data[h].hcode): yield t.data[h].key iterator values*[A, B](t: Table[A, B]): B = ## iterates over any value in the table `t`. for h in 0..high(t.data): - if t.data[h].slot == seFilled: yield t.data[h].val + if isFilled(t.data[h].hcode): yield t.data[h].val iterator mvalues*[A, B](t: var Table[A, B]): var B = ## iterates over any value in the table `t`. The values can be modified. for h in 0..high(t.data): - if t.data[h].slot == seFilled: yield t.data[h].val + if isFilled(t.data[h].hcode): yield t.data[h].val const growthFactor = 2 @@ -121,26 +128,57 @@ proc mustRehash(length, counter: int): bool {.inline.} = assert(length > counter) result = (length * 2 < counter * 3) or (length - counter < 4) +proc rightSize*(count: int): int {.inline.} = + ## Return the value of `initialSize` to support `count` items. + ## + ## If more items are expected to be added, simply add that + ## expected extra amount to the parameter before calling this. + ## + ## Internally, we want mustRehash(rightSize(x), x) == false. + result = nextPowerOfTwo(count * 3 div 2 + 4) + proc nextTry(h, maxHash: THash): THash {.inline.} = - result = ((5 * h) + 1) and maxHash + result = (h + 1) and maxHash + +template rawGetKnownHCImpl() {.dirty.} = + var h: THash = hc and high(t.data) # start with real hash value + while isFilled(t.data[h].hcode): + # Compare hc THEN key with boolean short circuit. This makes the common case + # zero ==key's for missing (e.g.inserts) and exactly one ==key for present. + # It does slow down succeeding lookups by one extra THash cmp&and..usually + # just a few clock cycles, generally worth it for any non-integer-like A. + if t.data[h].hcode == hc and t.data[h].key == key: + return h + h = nextTry(h, high(t.data)) + result = -1 - h # < 0 => MISSING; insert idx = -1 - result template rawGetImpl() {.dirty.} = - var h: THash = hash(key) and high(t.data) # start with real hash value - while t.data[h].slot != seEmpty: - if t.data[h].key == key and t.data[h].slot == seFilled: - return h + hc = hash(key) + if hc == 0: # This almost never taken branch should be very predictable. + hc = 314159265 # Value doesn't matter; Any non-zero favorite is fine. + rawGetKnownHCImpl() + +template rawGetDeepImpl() {.dirty.} = # Search algo for unconditional add + hc = hash(key) + if hc == 0: + hc = 314159265 + var h: THash = hc and high(t.data) + while isFilled(t.data[h].hcode): h = nextTry(h, high(t.data)) - result = -1 + result = h template rawInsertImpl() {.dirty.} = - var h: THash = hash(key) and high(data) - while data[h].slot == seFilled: - h = nextTry(h, high(data)) data[h].key = key data[h].val = val - data[h].slot = seFilled + data[h].hcode = hc + +proc rawGetKnownHC[A, B](t: Table[A, B], key: A, hc: THash): int {.inline.} = + rawGetKnownHCImpl() + +proc rawGetDeep[A, B](t: Table[A, B], key: A, hc: var THash): int {.inline.} = + rawGetDeepImpl() -proc rawGet[A, B](t: Table[A, B], key: A): int = +proc rawGet[A, B](t: Table[A, B], key: A, hc: var THash): int {.inline.} = rawGetImpl() proc `[]`*[A, B](t: Table[A, B], key: A): B = @@ -148,50 +186,62 @@ proc `[]`*[A, B](t: Table[A, B], key: A): B = ## default empty value for the type `B` is returned ## and no exception is raised. One can check with ``hasKey`` whether the key ## exists. - var index = rawGet(t, key) + var hc: THash + 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 = ## retrieves the value at ``t[key]``. The value can be modified. ## If `key` is not in `t`, the ``EInvalidKey`` exception is raised. - var index = rawGet(t, key) + var hc: THash + var index = rawGet(t, key, hc) if index >= 0: result = t.data[index].val else: raise newException(KeyError, "key not found: " & $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: THash = hash(key) and high(t.data) - while t.data[h].slot != seEmpty: - if t.data[h].key == key and t.data[h].slot == seFilled: + while isFilled(t.data[h].hcode): + if t.data[h].key == key: yield t.data[h].val h = nextTry(h, high(t.data)) proc hasKey*[A, B](t: Table[A, B], key: A): bool = ## returns true iff `key` is in the table `t`. - result = rawGet(t, key) >= 0 + var hc: THash + result = rawGet(t, key, hc) >= 0 proc rawInsert[A, B](t: var Table[A, B], data: var KeyValuePairSeq[A, B], - key: A, val: B) = + key: A, val: B, hc: THash, h: THash) = rawInsertImpl() proc enlarge[A, B](t: var Table[A, B]) = var n: KeyValuePairSeq[A, B] newSeq(n, len(t.data) * growthFactor) - for i in countup(0, high(t.data)): - if t.data[i].slot == seFilled: rawInsert(t, n, t.data[i].key, t.data[i].val) swap(t.data, n) + for i in countup(0, high(n)): + if isFilled(n[i].hcode): + var j = -1 - rawGetKnownHC(t, n[i].key, n[i].hcode) + rawInsert(t, t.data, n[i].key, n[i].val, n[i].hcode, j) template addImpl() {.dirty.} = if mustRehash(len(t.data), t.counter): enlarge(t) - rawInsert(t, t.data, key, val) + var hc: THash + var j = rawGetDeep(t, key, hc) + rawInsert(t, t.data, key, val, hc, j) inc(t.counter) template putImpl() {.dirty.} = - var index = rawGet(t, key) + var hc: THash + var index = rawGet(t, key, hc) if index >= 0: t.data[index].val = val else: - addImpl() + if mustRehash(len(t.data), t.counter): + enlarge(t) + index = rawGetKnownHC(t, key, hc) + rawInsert(t, t.data, key, val, hc, -1 - index) + inc(t.counter) when false: # not yet used: @@ -213,13 +263,30 @@ proc `[]=`*[A, B](t: var Table[A, B], key: A, val: B) = proc add*[A, B](t: var Table[A, B], key: A, val: B) = ## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists. addImpl() - + +template doWhile(a: expr, b: stmt): stmt = + while true: + b + if not a: break + proc del*[A, B](t: var Table[A, B], key: A) = ## deletes `key` from hash table `t`. - let index = rawGet(t, key) - if index >= 0: - t.data[index].slot = seDeleted + var hc: THash + var i = rawGet(t, key, hc) + let msk = high(t.data) + if i >= 0: + t.data[i].hcode = 0 dec(t.counter) + while true: # KnuthV3 Algo6.4R adapted for i=i+1 instead of i=i-1 + var j = i # The correctness of this depends on (h+1) in nextTry, + var r = j # though may be adaptable to other simple sequences. + t.data[i].hcode = 0 # mark current EMPTY + doWhile ((i >= r and r > j) or (r > j and j > i) or (j > i and i >= r)): + i = (i + 1) and msk # increment mod table size + if isEmpty(t.data[i].hcode): # end of collision cluster; So all done + return + r = t.data[i].hcode and msk # "home" location of key@i + t.data[j] = t.data[i] # data[j] will be marked EMPTY next loop proc initTable*[A, B](initialSize=64): Table[A, B] = ## creates a new hash table that is empty. @@ -234,7 +301,7 @@ proc initTable*[A, B](initialSize=64): Table[A, B] = proc toTable*[A, B](pairs: openArray[tuple[key: A, val: B]]): Table[A, B] = ## creates a new hash table that contains the given `pairs`. - result = initTable[A, B](nextPowerOfTwo(pairs.len+10)) + result = initTable[A, B](rightSize(pairs.len)) for key, val in items(pairs): result[key] = val template dollarImpl(): stmt {.dirty.} = @@ -252,7 +319,7 @@ template dollarImpl(): stmt {.dirty.} = proc `$`*[A, B](t: Table[A, B]): string = ## The `$` operator for hash tables. dollarImpl() - + template equalsImpl() = if s.counter == t.counter: # different insertion orders mean different 'data' seqs, so we have @@ -262,10 +329,10 @@ template equalsImpl() = if not t.hasKey(key): return false if t[key] != val: return false return true - + proc `==`*[A, B](s, t: Table[A, B]): bool = equalsImpl() - + proc indexBy*[A, B, C](collection: A, index: proc(x: B): C): Table[C, B] = ## Index the collection with the proc provided. # TODO: As soon as supported, change collection: A to collection: A[B] @@ -280,28 +347,28 @@ proc len*[A, B](t: TableRef[A, B]): int = iterator pairs*[A, B](t: TableRef[A, B]): tuple[key: A, val: B] = ## iterates over any (key, value) pair in the table `t`. for h in 0..high(t.data): - if t.data[h].slot == seFilled: yield (t.data[h].key, t.data[h].val) + if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) iterator mpairs*[A, B](t: TableRef[A, B]): tuple[key: A, val: var B] = ## iterates over any (key, value) pair in the table `t`. The values ## can be modified. for h in 0..high(t.data): - if t.data[h].slot == seFilled: yield (t.data[h].key, t.data[h].val) + if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) iterator keys*[A, B](t: TableRef[A, B]): A = ## iterates over any key in the table `t`. for h in 0..high(t.data): - if t.data[h].slot == seFilled: yield t.data[h].key + if isFilled(t.data[h].hcode): yield t.data[h].key iterator values*[A, B](t: TableRef[A, B]): B = ## iterates over any value in the table `t`. for h in 0..high(t.data): - if t.data[h].slot == seFilled: yield t.data[h].val + if isFilled(t.data[h].hcode): yield t.data[h].val iterator mvalues*[A, B](t: TableRef[A, B]): var B = ## iterates over any value in the table `t`. The values can be modified. for h in 0..high(t.data): - if t.data[h].slot == seFilled: yield t.data[h].val + 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`, @@ -326,7 +393,7 @@ proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: B) = proc add*[A, B](t: TableRef[A, B], key: A, val: B) = ## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists. t[].add(key, val) - + proc del*[A, B](t: TableRef[A, B], key: A) = ## deletes `key` from hash table `t`. t[].del(key) @@ -360,7 +427,7 @@ proc newTableFrom*[A, B, C](collection: A, index: proc(x: B): C): TableRef[C, B] type OrderedKeyValuePair[A, B] = tuple[ - slot: SlotEnum, next: int, key: A, val: B] + hcode: THash, next: int, key: A, val: B] OrderedKeyValuePairSeq[A, B] = seq[OrderedKeyValuePair[A, B]] OrderedTable* {. myShallow.}[A, B] = object ## table that remembers insertion order @@ -378,7 +445,7 @@ template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} = var h = t.first while h >= 0: var nxt = t.data[h].next - if t.data[h].slot == seFilled: yieldStmt + if isFilled(t.data[h].hcode): yieldStmt h = nxt iterator pairs*[A, B](t: OrderedTable[A, B]): tuple[key: A, val: B] = @@ -409,7 +476,13 @@ iterator mvalues*[A, B](t: var OrderedTable[A, B]): var B = forAllOrderedPairs: yield t.data[h].val -proc rawGet[A, B](t: OrderedTable[A, B], key: A): int = +proc rawGetKnownHC[A, B](t: OrderedTable[A, B], key: A, hc: THash): int = + rawGetKnownHCImpl() + +proc rawGetDeep[A, B](t: OrderedTable[A, B], key: A, hc: var THash): int {.inline.} = + rawGetDeepImpl() + +proc rawGet[A, B](t: OrderedTable[A, B], key: A, hc: var THash): int = rawGetImpl() proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B = @@ -433,7 +506,7 @@ proc hasKey*[A, B](t: OrderedTable[A, B], key: A): bool = proc rawInsert[A, B](t: var OrderedTable[A, B], data: var OrderedKeyValuePairSeq[A, B], - key: A, val: B) = + key: A, val: B, hc: THash, h: THash) = rawInsertImpl() data[h].next = -1 if t.first < 0: t.first = h @@ -446,12 +519,13 @@ proc enlarge[A, B](t: var OrderedTable[A, B]) = var h = t.first t.first = -1 t.last = -1 + swap(t.data, n) while h >= 0: - var nxt = t.data[h].next - if t.data[h].slot == seFilled: - rawInsert(t, n, t.data[h].key, t.data[h].val) + var nxt = n[h].next + if isFilled(n[h].hcode): + var j = -1 - rawGetKnownHC(t, n[h].key, n[h].hcode) + rawInsert(t, t.data, n[h].key, n[h].val, n[h].hcode, j) h = nxt - swap(t.data, n) proc `[]=`*[A, B](t: var OrderedTable[A, B], key: A, val: B) = ## puts a (key, value)-pair into `t`. @@ -476,7 +550,7 @@ proc initOrderedTable*[A, B](initialSize=64): OrderedTable[A, B] = proc toOrderedTable*[A, B](pairs: openArray[tuple[key: A, val: B]]): OrderedTable[A, B] = ## creates a new ordered hash table that contains the given `pairs`. - result = initOrderedTable[A, B](nextPowerOfTwo(pairs.len+10)) + result = initOrderedTable[A, B](rightSize(pairs.len)) for key, val in items(pairs): result[key] = val proc `$`*[A, B](t: OrderedTable[A, B]): string = @@ -537,7 +611,7 @@ template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} = var h = t.first while h >= 0: var nxt = t.data[h].next - if t.data[h].slot == seFilled: yieldStmt + if isFilled(t.data[h].hcode): yieldStmt h = nxt iterator pairs*[A, B](t: OrderedTableRef[A, B]): tuple[key: A, val: B] = @@ -604,7 +678,7 @@ proc newOrderedTable*[A, B](initialSize=64): OrderedTableRef[A, B] = proc newOrderedTable*[A, B](pairs: openArray[tuple[key: A, val: B]]): OrderedTableRef[A, B] = ## creates a new ordered hash table that contains the given `pairs`. - result = newOrderedTable[A, B](nextPowerOfTwo(pairs.len+10)) + result = newOrderedTable[A, B](rightSize(pairs.len)) for key, val in items(pairs): result[key] = val proc `$`*[A, B](t: OrderedTableRef[A, B]): string = @@ -665,7 +739,7 @@ proc rawGet[A](t: CountTable[A], key: A): int = while t.data[h].val != 0: if t.data[h].key == key: return h h = nextTry(h, high(t.data)) - result = -1 + 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`, @@ -702,21 +776,27 @@ proc enlarge[A](t: var CountTable[A]) = proc `[]=`*[A](t: var CountTable[A], key: A, val: int) = ## puts a (key, value)-pair into `t`. `val` has to be positive. assert val > 0 - putImpl() + var h = rawGet(t, key) + if h >= 0: + t.data[h].val = val + else: + h = -1 - h + t.data[h].key = key + t.data[h].val = val proc initCountTable*[A](initialSize=64): CountTable[A] = ## creates a new count table that is empty. ## ## `initialSize` needs to be a power of two. If you need to accept runtime ## values for this you could use the ``nextPowerOfTwo`` proc from the - ## `math `_ module. + ## `math `_ module or the ``rightSize`` method in this module. assert isPowerOfTwo(initialSize) result.counter = 0 newSeq(result.data, initialSize) proc toCountTable*[A](keys: openArray[A]): CountTable[A] = ## creates a new count table with every key in `keys` having a count of 1. - result = initCountTable[A](nextPowerOfTwo(keys.len+10)) + result = initCountTable[A](rightSize(keys.len)) for key in items(keys): result[key] = 1 proc `$`*[A](t: CountTable[A]): string = @@ -827,13 +907,13 @@ proc newCountTable*[A](initialSize=64): CountTableRef[A] = ## ## `initialSize` needs to be a power of two. If you need to accept runtime ## values for this you could use the ``nextPowerOfTwo`` proc from the - ## `math `_ module. + ## `math `_ module or the ``rightSize`` method in this module. new(result) result[] = initCountTable[A](initialSize) proc newCountTable*[A](keys: openArray[A]): CountTableRef[A] = ## creates a new count table with every key in `keys` having a count of 1. - result = newCountTable[A](nextPowerOfTwo(keys.len+10)) + result = newCountTable[A](rightSize(keys.len)) for key in items(keys): result[key] = 1 proc `$`*[A](t: CountTableRef[A]): string = -- cgit 1.4.1-2-gfad0 From 49d88cee68269bf6ed5373777bb76df5c18af495 Mon Sep 17 00:00:00 2001 From: Charles Blake Date: Thu, 12 Feb 2015 06:44:09 -0500 Subject: Oops - missed updates to a few later rawGet()s. --- lib/pure/collections/tables.nim | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index da9d21050..4858ca2b5 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -246,7 +246,8 @@ template putImpl() {.dirty.} = when false: # not yet used: template hasKeyOrPutImpl() {.dirty.} = - var index = rawGet(t, key) + var hc: THash + var index = rawGet(t, key, hc) if index >= 0: t.data[index].val = val result = true @@ -490,19 +491,22 @@ proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B = ## default empty value for the type `B` is returned ## and no exception is raised. One can check with ``hasKey`` whether the key ## exists. - var index = rawGet(t, key) + var hc: THash + var index = rawGet(t, key, hc) if index >= 0: result = t.data[index].val proc mget*[A, B](t: var OrderedTable[A, B], key: A): var B = ## retrieves the value at ``t[key]``. The value can be modified. ## If `key` is not in `t`, the ``EInvalidKey`` exception is raised. - var index = rawGet(t, key) + var hc: THash + var index = rawGet(t, key, hc) if index >= 0: result = t.data[index].val else: raise newException(KeyError, "key not found: " & $key) proc hasKey*[A, B](t: OrderedTable[A, B], key: A): bool = ## returns true iff `key` is in the table `t`. - result = rawGet(t, key) >= 0 + var hc: THash + result = rawGet(t, key, hc) >= 0 proc rawInsert[A, B](t: var OrderedTable[A, B], data: var OrderedKeyValuePairSeq[A, B], -- cgit 1.4.1-2-gfad0 From a21d7c681d9f61f15beddb97fabba0ed6b72640d Mon Sep 17 00:00:00 2001 From: Charles Blake Date: Thu, 12 Feb 2015 06:57:39 -0500 Subject: New probe seq yields a non-bug swap of 1st 2 keys. --- tests/table/ttables.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/table/ttables.nim b/tests/table/ttables.nim index de4aaed5e..8534e6767 100644 --- a/tests/table/ttables.nim +++ b/tests/table/ttables.nim @@ -47,7 +47,7 @@ block tableTest1: for y in 0..1: assert t[(x,y)] == $x & $y assert($t == - "{(x: 0, y: 0): 00, (x: 0, y: 1): 01, (x: 1, y: 0): 10, (x: 1, y: 1): 11}") + "{(x: 0, y: 1): 01, (x: 0, y: 0): 00, (x: 1, y: 0): 10, (x: 1, y: 1): 11}") block tableTest2: var t = initTable[string, float]() -- cgit 1.4.1-2-gfad0 From 55ab6cc2b37491d397d7f207a759b48882db6d6d Mon Sep 17 00:00:00 2001 From: Hans Raaf Date: Fri, 13 Feb 2015 00:10:24 +0100 Subject: Disable -pthread for linker on OSX The -pthread is not needed on Darwin/OS X and the Apple compilers give a warning about this if you use --threads:on with the Nim compiler. --- lib/system/threads.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/system/threads.nim b/lib/system/threads.nim index 496c31af1..4e0720007 100644 --- a/lib/system/threads.nim +++ b/lib/system/threads.nim @@ -95,7 +95,9 @@ when defined(windows): importc: "TlsGetValue", stdcall, dynlib: "kernel32".} else: - {.passL: "-pthread".} + when not defined(macosx): + {.passL: "-pthread".} + {.passC: "-pthread".} type -- cgit 1.4.1-2-gfad0 From d129e8f6c6b6b39ed76b2e71f0179a1eb6171f1d Mon Sep 17 00:00:00 2001 From: Charles Blake Date: Fri, 13 Feb 2015 08:28:58 -0500 Subject: Update doc comments to mention rightSize. --- lib/pure/collections/sets.nim | 12 ++++++------ lib/pure/collections/tables.nim | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index 33fec1a18..c25161ed0 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -313,9 +313,9 @@ proc init*[A](s: var HashSet[A], initialSize=64) = ## Initializes a hash set. ## ## The `initialSize` parameter needs to be a power of two. You can use - ## `math.nextPowerOfTwo() `_ to guarantee that at - ## runtime. All set variables have to be initialized before you can use them - ## with other procs from this module with the exception of `isValid() + ## `math.nextPowerOfTwo() `_ or `rightSize` to + ## guarantee that at runtime. All set variables must be initialized before + ## use with other procs from this module with the exception of `isValid() ## <#isValid,TSet[A]>`_ and `len() <#len,TSet[A]>`_. ## ## You can call this proc on a previously initialized hash set, which will @@ -719,9 +719,9 @@ proc init*[A](s: var OrderedSet[A], initialSize=64) = ## Initializes an ordered hash set. ## ## The `initialSize` parameter needs to be a power of two. You can use - ## `math.nextPowerOfTwo() `_ to guarantee that at - ## runtime. All set variables have to be initialized before you can use them - ## with other procs from this module with the exception of `isValid() + ## `math.nextPowerOfTwo() `_ or `rightSize` to + ## guarantee that at runtime. All set variables must be initialized before + ## use with other procs from this module with the exception of `isValid() ## <#isValid,TOrderedSet[A]>`_ and `len() <#len,TOrderedSet[A]>`_. ## ## You can call this proc on a previously initialized ordered hash set to diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 4858ca2b5..7ed0e46cc 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -294,7 +294,7 @@ proc initTable*[A, B](initialSize=64): Table[A, B] = ## ## `initialSize` needs to be a power of two. If you need to accept runtime ## values for this you could use the ``nextPowerOfTwo`` proc from the - ## `math `_ module. + ## `math `_ module or the ``rightSize`` proc from this module. assert isPowerOfTwo(initialSize) result.counter = 0 newSeq(result.data, initialSize) @@ -544,7 +544,7 @@ proc initOrderedTable*[A, B](initialSize=64): OrderedTable[A, B] = ## ## `initialSize` needs to be a power of two. If you need to accept runtime ## values for this you could use the ``nextPowerOfTwo`` proc from the - ## `math `_ module. + ## `math `_ module or the ``rightSize`` proc from this module. assert isPowerOfTwo(initialSize) result.counter = 0 result.first = -1 @@ -675,7 +675,7 @@ proc newOrderedTable*[A, B](initialSize=64): OrderedTableRef[A, B] = ## ## `initialSize` needs to be a power of two. If you need to accept runtime ## values for this you could use the ``nextPowerOfTwo`` proc from the - ## `math `_ module. + ## `math `_ module or the ``rightSize`` proc from this module. new(result) result[] = initOrderedTable[A, B]() @@ -793,7 +793,7 @@ proc initCountTable*[A](initialSize=64): CountTable[A] = ## ## `initialSize` needs to be a power of two. If you need to accept runtime ## values for this you could use the ``nextPowerOfTwo`` proc from the - ## `math `_ module or the ``rightSize`` method in this module. + ## `math `_ module or the ``rightSize`` proc in this module. assert isPowerOfTwo(initialSize) result.counter = 0 newSeq(result.data, initialSize) -- cgit 1.4.1-2-gfad0 From 0a1bc0e9cd30ef1a3654e7637795ed22010e7977 Mon Sep 17 00:00:00 2001 From: Charles Blake Date: Fri, 13 Feb 2015 08:33:15 -0500 Subject: Update a use of initTable to avoid initial enlarge. --- tests/manyloc/argument_parser/argument_parser.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/manyloc/argument_parser/argument_parser.nim b/tests/manyloc/argument_parser/argument_parser.nim index af671cd85..a507a08e4 100644 --- a/tests/manyloc/argument_parser/argument_parser.nim +++ b/tests/manyloc/argument_parser/argument_parser.nim @@ -302,7 +302,7 @@ template build_specification_lookup(): ## Returns the table used to keep pointers to all of the specifications. var result {.gensym.}: OrderedTable[string, ptr Tparameter_specification] result = initOrderedTable[string, ptr Tparameter_specification]( - nextPowerOfTwo(expected.len)) + tables.rightSize(expected.len)) for i in 0..expected.len-1: for param_to_detect in expected[i].names: if result.hasKey(param_to_detect): -- cgit 1.4.1-2-gfad0 From 39b98fede3d982c8599c9ed80f1df1d56a4bcf18 Mon Sep 17 00:00:00 2001 From: Charles Blake Date: Fri, 13 Feb 2015 08:50:26 -0500 Subject: New probe seq swaps 1st two keys. Fix in cmp. --- tests/collections/ttables.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/collections/ttables.nim b/tests/collections/ttables.nim index de4aaed5e..8534e6767 100644 --- a/tests/collections/ttables.nim +++ b/tests/collections/ttables.nim @@ -47,7 +47,7 @@ block tableTest1: for y in 0..1: assert t[(x,y)] == $x & $y assert($t == - "{(x: 0, y: 0): 00, (x: 0, y: 1): 01, (x: 1, y: 0): 10, (x: 1, y: 1): 11}") + "{(x: 0, y: 1): 01, (x: 0, y: 0): 00, (x: 1, y: 0): 10, (x: 1, y: 1): 11}") block tableTest2: var t = initTable[string, float]() -- cgit 1.4.1-2-gfad0 From 5068a5aa016fef0b65c7cd6af27eeeefda0e5c95 Mon Sep 17 00:00:00 2001 From: Charles Blake Date: Fri, 13 Feb 2015 14:10:09 -0500 Subject: assignment -> shallowCopy for efficiency. --- lib/pure/collections/sets.nim | 2 +- lib/pure/collections/tables.nim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index c25161ed0..4a20d00a4 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -276,7 +276,7 @@ proc excl*[A](s: var HashSet[A], key: A) = if isEmpty(s.data[i].hcode): # end of collision cluster; So all done return r = s.data[i].hcode and msk # "home" location of key@i - s.data[j] = s.data[i] # data[j] will be marked EMPTY next loop + shallowCopy(s.data[j], s.data[i]) # data[j] will be marked EMPTY next loop proc excl*[A](s: var HashSet[A], other: HashSet[A]) = ## Excludes everything in `other` from `s`. diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 58305b010..25fe306c0 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -287,7 +287,7 @@ proc del*[A, B](t: var Table[A, B], key: A) = if isEmpty(t.data[i].hcode): # end of collision cluster; So all done return r = t.data[i].hcode and msk # "home" location of key@i - t.data[j] = t.data[i] # data[j] will be marked EMPTY next loop + shallowCopy(t.data[j], t.data[i]) # data[j] will be marked EMPTY next loop proc initTable*[A, B](initialSize=64): Table[A, B] = ## creates a new hash table that is empty. -- cgit 1.4.1-2-gfad0 From fb718f2d04f10c63562e14c40da28b8da34d9714 Mon Sep 17 00:00:00 2001 From: def Date: Sat, 14 Feb 2015 19:00:11 +0100 Subject: nimrod -> nim in some filenames --- compiler/installer.ini | 8 +-- compiler/nim.nim | 4 +- config/nimrod.cfg | 136 ---------------------------------------------- icons/nim.ico | Bin 0 -> 30319 bytes icons/nim.rc | 3 + icons/nim.res | Bin 0 -> 30830 bytes icons/nim_icon.o | Bin 0 -> 30830 bytes icons/nimrod.ico | Bin 30319 -> 0 bytes icons/nimrod.rc | 3 - icons/nimrod.res | Bin 30830 -> 0 bytes icons/nimrod_icon.o | Bin 30830 -> 0 bytes tests/rodfiles/nim.cfg | 2 + tests/rodfiles/nimrod.cfg | 2 - tools/niminst/nsis.tmpl | 4 +- 14 files changed, 13 insertions(+), 149 deletions(-) delete mode 100644 config/nimrod.cfg create mode 100644 icons/nim.ico create mode 100644 icons/nim.rc create mode 100644 icons/nim.res create mode 100644 icons/nim_icon.o delete mode 100644 icons/nimrod.ico delete mode 100644 icons/nimrod.rc delete mode 100644 icons/nimrod.res delete mode 100644 icons/nimrod_icon.o create mode 100644 tests/rodfiles/nim.cfg delete mode 100644 tests/rodfiles/nimrod.cfg diff --git a/compiler/installer.ini b/compiler/installer.ini index dcf9aa52f..48cd0b3b9 100644 --- a/compiler/installer.ini +++ b/compiler/installer.ini @@ -51,10 +51,10 @@ Files: "configure;makefile" Files: "*.ini" Files: "koch.nim" -Files: "icons/nimrod.ico" -Files: "icons/nimrod.rc" -Files: "icons/nimrod.res" -Files: "icons/nimrod_icon.o" +Files: "icons/nim.ico" +Files: "icons/nim.rc" +Files: "icons/nim.res" +Files: "icons/nim_icon.o" Files: "icons/koch.ico" Files: "icons/koch.rc" Files: "icons/koch.res" diff --git a/compiler/nim.nim b/compiler/nim.nim index 617758b2d..215f1986e 100644 --- a/compiler/nim.nim +++ b/compiler/nim.nim @@ -9,9 +9,9 @@ when defined(gcc) and defined(windows): when defined(x86): - {.link: "icons/nimrod.res".} + {.link: "icons/nim.res".} else: - {.link: "icons/nimrod_icon.o".} + {.link: "icons/nim_icon.o".} import commands, lexer, condsyms, options, msgs, nversion, nimconf, ropes, diff --git a/config/nimrod.cfg b/config/nimrod.cfg deleted file mode 100644 index 62a7b16cc..000000000 --- a/config/nimrod.cfg +++ /dev/null @@ -1,136 +0,0 @@ -# Configuration file for the Nim Compiler. -# (c) 2015 Andreas Rumpf - -# Feel free to edit the default values as you need. - -# You may set environment variables with -# @putenv "key" "val" -# Environment variables cannot be used in the options, however! - -cc = gcc - -# example of how to setup a cross-compiler: -arm.linux.gcc.exe = "arm-linux-gcc" -arm.linux.gcc.linkerexe = "arm-linux-gcc" - -cs:partial - -path="$lib/core" -path="$lib/pure" -path="$lib/pure/collections" -path="$lib/pure/concurrency" -path="$lib/impure" -path="$lib/wrappers" -# path="$lib/wrappers/cairo" -# path="$lib/wrappers/gtk" -# path="$lib/wrappers/lua" -# path="$lib/wrappers/opengl" -path="$lib/wrappers/pcre" -path="$lib/wrappers/readline" -path="$lib/wrappers/sdl" -# path="$lib/wrappers/x11" -path="$lib/wrappers/zip" -path="$lib/wrappers/libffi" -path="$lib/windows" -path="$lib/posix" -path="$lib/js" -path="$lib/pure/unidecode" - -@if nimbabel: - babelpath="$home/.babel/pkgs/" -@end - -@if release or quick: - obj_checks:off - field_checks:off - range_checks:off - bound_checks:off - overflow_checks:off - assertions:off - stacktrace:off - linetrace:off - debugger:off - line_dir:off - dead_code_elim:on -@end - -@if release: - opt:speed -@end - -# additional options always passed to the compiler: ---parallel_build: "0" # 0 to auto-detect number of processors - -hint[LineTooLong]=off -#hint[XDeclaredButNotUsed]=off - -@if unix: - @if not bsd: - # -fopenmp - gcc.options.linker = "-ldl" - gpp.options.linker = "-ldl" - clang.options.linker = "-ldl" - tcc.options.linker = "-ldl" - @end - @if bsd or haiku: - # BSD got posix_spawn only recently, so we deactivate it for osproc: - define:useFork - # at least NetBSD has problems with thread local storage: - tlsEmulation:on - @end -@end - -# Configuration for the Intel C/C++ compiler: -@if windows: - icl.options.speed = "/Ox /arch:SSE2" - icl.options.always = "/nologo" -@end - -# Configuration for the GNU C/C++ compiler: -@if windows: - #gcc.path = r"$nimrod\dist\mingw\bin" - @if gcc: - tlsEmulation:on - @end -@end - -@if macosx: - cc = clang - tlsEmulation:on - gcc.options.always = "-w -fasm-blocks" - gpp.options.always = "-w -fasm-blocks -fpermissive" -@else: - gcc.options.always = "-w" - gpp.options.always = "-w -fpermissive" -@end - -gcc.options.speed = "-O3 -fno-strict-aliasing" -gcc.options.size = "-Os" -gcc.options.debug = "-g3 -O0" - -gpp.options.speed = "-O3 -fno-strict-aliasing" -gpp.options.size = "-Os" -gpp.options.debug = "-g3 -O0" -#passl = "-pg" - -# Configuration for the LLVM GCC compiler: -llvm_gcc.options.debug = "-g" -llvm_gcc.options.always = "-w" -llvm_gcc.options.speed = "-O2" -llvm_gcc.options.size = "-Os" - -# Configuration for the LLVM CLang compiler: -clang.options.debug = "-g" -clang.options.always = "-w" -clang.options.speed = "-O3" -clang.options.size = "-Os" - -# Configuration for the Visual C/C++ compiler: -vcc.options.linker = "/DEBUG /Zi /Fd\"$projectName.pdb\" /F33554432" # set the stack size to 8 MB -vcc.options.debug = "/Zi /Fd\"$projectName.pdb\"" -vcc.options.always = "/nologo" -vcc.options.speed = "/Ox /arch:SSE2" -vcc.options.size = "/O1" - -# Configuration for the Tiny C Compiler: -tcc.options.always = "-w" diff --git a/icons/nim.ico b/icons/nim.ico new file mode 100644 index 000000000..58cc4314c Binary files /dev/null and b/icons/nim.ico differ diff --git a/icons/nim.rc b/icons/nim.rc new file mode 100644 index 000000000..c053e08e9 --- /dev/null +++ b/icons/nim.rc @@ -0,0 +1,3 @@ +nimicon ICON "nim.ico" + + diff --git a/icons/nim.res b/icons/nim.res new file mode 100644 index 000000000..6eddd053b Binary files /dev/null and b/icons/nim.res differ diff --git a/icons/nim_icon.o b/icons/nim_icon.o new file mode 100644 index 000000000..c8c364412 Binary files /dev/null and b/icons/nim_icon.o differ diff --git a/icons/nimrod.ico b/icons/nimrod.ico deleted file mode 100644 index 58cc4314c..000000000 Binary files a/icons/nimrod.ico and /dev/null differ diff --git a/icons/nimrod.rc b/icons/nimrod.rc deleted file mode 100644 index 6f36b8145..000000000 --- a/icons/nimrod.rc +++ /dev/null @@ -1,3 +0,0 @@ -nimrodicon ICON "nimrod.ico" - - diff --git a/icons/nimrod.res b/icons/nimrod.res deleted file mode 100644 index 6eddd053b..000000000 Binary files a/icons/nimrod.res and /dev/null differ diff --git a/icons/nimrod_icon.o b/icons/nimrod_icon.o deleted file mode 100644 index c8c364412..000000000 Binary files a/icons/nimrod_icon.o and /dev/null differ diff --git a/tests/rodfiles/nim.cfg b/tests/rodfiles/nim.cfg new file mode 100644 index 000000000..78fc8db64 --- /dev/null +++ b/tests/rodfiles/nim.cfg @@ -0,0 +1,2 @@ +--nimcache:"$projectPath/nimcache" +--symbolFiles:on diff --git a/tests/rodfiles/nimrod.cfg b/tests/rodfiles/nimrod.cfg deleted file mode 100644 index 78fc8db64..000000000 --- a/tests/rodfiles/nimrod.cfg +++ /dev/null @@ -1,2 +0,0 @@ ---nimcache:"$projectPath/nimcache" ---symbolFiles:on diff --git a/tools/niminst/nsis.tmpl b/tools/niminst/nsis.tmpl index 23bbf3ac9..aba1e581f 100644 --- a/tools/niminst/nsis.tmpl +++ b/tools/niminst/nsis.tmpl @@ -50,8 +50,8 @@ SetCompressor /SOLID /FINAL lzma ; Installer and Uninstaller Icons - ; Icon "nimrod.ico" - ; UninstallIcon "nimrod.ico" + ; Icon "nim.ico" + ; UninstallIcon "nim.ico" ; Set installation details to be shown by default ShowInstDetails show -- cgit 1.4.1-2-gfad0 From 512db9aea6ac77234e2ea6a48b1cc20e6b24a687 Mon Sep 17 00:00:00 2001 From: def Date: Sat, 14 Feb 2015 19:57:32 +0100 Subject: Fix documentation a bit in unicode --- lib/pure/unicode.nim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim index b892ec8b4..42e6a3195 100644 --- a/lib/pure/unicode.nim +++ b/lib/pure/unicode.nim @@ -1235,11 +1235,12 @@ proc reversed*(s: string): string = ## returns the reverse of `s`, interpreting it as unicode characters. Unicode ## combining characters are correctly interpreted as well: ## - ## .. code-block: + ## .. code-block:: nim + ## ## assert reversed("Reverse this!") == "!siht esreveR" ## assert reversed("先秦兩漢") == "漢兩秦先" ## assert reversed("as⃝df̅") == "f̅ds⃝a" - ## assert reversed("a⃞b⃞c⃞") == "c⃞b⃞a⃞" + ## assert reversed("a⃞b⃞c⃞") == "c⃞b⃞a⃞" var i = 0 lastI = 0 -- cgit 1.4.1-2-gfad0 From 5b32e31a279e2f6a7b19328fbc7c2d13079052f3 Mon Sep 17 00:00:00 2001 From: "M.Yasoob Ullah Khalid ☺" Date: Sun, 15 Feb 2015 01:03:11 +0500 Subject: It's 2015 :+1: --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index b08fa2291..2252ea52b 100644 --- a/readme.md +++ b/readme.md @@ -62,7 +62,7 @@ allowing you to create commercial applications. Read copying.txt for more details. -Copyright (c) 2006-2014 Andreas Rumpf. +Copyright (c) 2006-2015 Andreas Rumpf. All rights reserved. # Build Status -- cgit 1.4.1-2-gfad0 From 6244cc2e4fb7aaabd14352a21add5236220a8198 Mon Sep 17 00:00:00 2001 From: def Date: Sat, 14 Feb 2015 23:43:31 +0100 Subject: Fix cross_calculator example --- examples/cross_calculator/nim_backend/backend.nim | 5 + examples/cross_calculator/nim_commandline/nim.cfg | 4 + .../nim_commandline/nimcalculator.nim | 109 +++++++++++++++++++++ .../cross_calculator/nim_commandline/readme.txt | 10 ++ .../cross_calculator/nimrod_backend/backend.nim | 5 - .../nimrod_commandline/nimcalculator.nim | 109 --------------------- .../cross_calculator/nimrod_commandline/nimrod.cfg | 4 - .../cross_calculator/nimrod_commandline/readme.txt | 10 -- 8 files changed, 128 insertions(+), 128 deletions(-) create mode 100644 examples/cross_calculator/nim_backend/backend.nim create mode 100644 examples/cross_calculator/nim_commandline/nim.cfg create mode 100644 examples/cross_calculator/nim_commandline/nimcalculator.nim create mode 100644 examples/cross_calculator/nim_commandline/readme.txt delete mode 100644 examples/cross_calculator/nimrod_backend/backend.nim delete mode 100644 examples/cross_calculator/nimrod_commandline/nimcalculator.nim delete mode 100644 examples/cross_calculator/nimrod_commandline/nimrod.cfg delete mode 100644 examples/cross_calculator/nimrod_commandline/readme.txt diff --git a/examples/cross_calculator/nim_backend/backend.nim b/examples/cross_calculator/nim_backend/backend.nim new file mode 100644 index 000000000..ffa4311f9 --- /dev/null +++ b/examples/cross_calculator/nim_backend/backend.nim @@ -0,0 +1,5 @@ +# Backend for the different user interfaces. + +proc myAdd*(x, y: int): int {.cdecl, exportc.} = + result = x + y + diff --git a/examples/cross_calculator/nim_commandline/nim.cfg b/examples/cross_calculator/nim_commandline/nim.cfg new file mode 100644 index 000000000..41c034430 --- /dev/null +++ b/examples/cross_calculator/nim_commandline/nim.cfg @@ -0,0 +1,4 @@ +# Nimrod configuration file. +# The file is used only to add the path of the backend to the compiler options. + +path="../nim_backend" diff --git a/examples/cross_calculator/nim_commandline/nimcalculator.nim b/examples/cross_calculator/nim_commandline/nimcalculator.nim new file mode 100644 index 000000000..69d62a90c --- /dev/null +++ b/examples/cross_calculator/nim_commandline/nimcalculator.nim @@ -0,0 +1,109 @@ +# Implements a command line interface against the backend. + +import backend, parseopt, strutils + +const + USAGE = """nimcalculator - Nimrod cross platform calculator + (beta version, only integer addition is supported!) + +Usage: + nimcalculator [options] [-a=value -b=value] +Options: + -a=value sets the integer value of the a parameter + -b=value sets the integer value of the b parameter + -h, --help shows this help + +If no options are used, an interactive mode is entered. +""" + +type + TCommand = enum # The possible types of operation + cmdParams, # Two valid parameters were provided + cmdInteractive # No parameters were provided, run interactive mode + + TParamConfig = object of RootObj + action: TCommand # store the type of operation + paramA, paramB: int # possibly store the valid parameters + + +proc parseCmdLine(): TParamConfig = + ## Parses the commandline. + ## + ## Returns a TParamConfig structure filled with the proper values or directly + ## calls quit() with the appropriate error message. + var + hasA = false + hasB = false + p = initOptParser() + key, val: TaintedString + + result.action = cmdInteractive # By default presume interactive mode. + try: + while true: + next p + key = p.key + val = p.val + + case p.kind + of cmdArgument: + stdout.write USAGE + quit "Erroneous argument detected: " & key, 1 + of cmdLongOption, cmdShortOption: + case key.normalize + of "help", "h": + stdout.write USAGE + quit 0 + of "a": + result.paramA = val.parseInt + hasA = true + of "b": + result.paramB = val.parseInt + hasB = true + else: + stdout.write USAGE + quit "Unexpected option: " & key, 2 + of cmdEnd: break + except ValueError: + stdout.write USAGE + quit "Invalid value " & val & " for parameter " & key, 3 + + if hasA and hasB: + result.action = cmdParams + elif hasA or hasB: + stdout.write USAGE + quit "Error: provide both A and B to operate in param mode", 4 + + +proc parseUserInput(question: string): int = + ## Parses a line of user input, showing question to the user first. + ## + ## If the user input is an empty line quit() is called. Returns the value + ## parsed as an integer. + while true: + echo question + let input = stdin.readLine + try: + result = input.parseInt + break + except ValueError: + if input.len < 1: quit "Blank line detected, quitting.", 0 + echo "Sorry, `$1' doesn't seem to be a valid integer" % input + +proc interactiveMode() = + ## Asks the user for two integer values, adds them and exits. + let + paramA = parseUserInput("Enter the first parameter (blank to exit):") + paramB = parseUserInput("Enter the second parameter (blank to exit):") + echo "Calculating... $1 + $2 = $3" % [$paramA, $paramB, + $backend.myAdd(paramA, paramB)] + + +when isMainModule: + ## Main entry point. + let opt = parseCmdLine() + if cmdParams == opt.action: + echo "Param mode: $1 + $2 = $3" % [$opt.paramA, $opt.paramB, + $backend.myAdd(opt.paramA, opt.paramB)] + else: + echo "Entering interactive addition mode" + interactiveMode() diff --git a/examples/cross_calculator/nim_commandline/readme.txt b/examples/cross_calculator/nim_commandline/readme.txt new file mode 100644 index 000000000..f95bd962e --- /dev/null +++ b/examples/cross_calculator/nim_commandline/readme.txt @@ -0,0 +1,10 @@ +In this directory you will find the nim commandline version of the +cross-calculator sample. + +The commandline interface can be used non interactively through switches, or +interactively when running the command without parameters. + +Compilation is fairly easy despite having the source split in different +directories. Thanks to the nim.cfg file, which adds the ../nim_backend +directory as a search path, you can compile and run the example just fine from +the command line with 'nim c -r nimcalculator.nim'. diff --git a/examples/cross_calculator/nimrod_backend/backend.nim b/examples/cross_calculator/nimrod_backend/backend.nim deleted file mode 100644 index ffa4311f9..000000000 --- a/examples/cross_calculator/nimrod_backend/backend.nim +++ /dev/null @@ -1,5 +0,0 @@ -# Backend for the different user interfaces. - -proc myAdd*(x, y: int): int {.cdecl, exportc.} = - result = x + y - diff --git a/examples/cross_calculator/nimrod_commandline/nimcalculator.nim b/examples/cross_calculator/nimrod_commandline/nimcalculator.nim deleted file mode 100644 index 440834ca8..000000000 --- a/examples/cross_calculator/nimrod_commandline/nimcalculator.nim +++ /dev/null @@ -1,109 +0,0 @@ -# Implements a command line interface against the backend. - -import backend, parseopt, strutils - -const - USAGE = """nimcalculator - Nimrod cross platform calculator - (beta version, only integer addition is supported!) - -Usage: - nimcalculator [options] [-a=value -b=value] -Options: - -a=value sets the integer value of the a parameter - -b=value sets the integer value of the b parameter - -h, --help shows this help - -If no options are used, an interactive mode is entered. -""" - -type - TCommand = enum # The possible types of operation - cmdParams, # Two valid parameters were provided - cmdInteractive # No parameters were provided, run interactive mode - - TParamConfig = object of TObject - action: TCommand # store the type of operation - paramA, paramB: int # possibly store the valid parameters - - -proc parseCmdLine(): TParamConfig = - ## Parses the commandline. - ## - ## Returns a TParamConfig structure filled with the proper values or directly - ## calls quit() with the appropriate error message. - var - hasA = false - hasB = false - p = initOptParser() - key, val: TaintedString - - result.action = cmdInteractive # By default presume interactive mode. - try: - while true: - next p - key = p.key - val = p.val - - case p.kind - of cmdArgument: - stdout.write USAGE - quit "Erroneous argument detected: " & key, 1 - of cmdLongOption, cmdShortOption: - case key.normalize - of "help", "h": - stdout.write USAGE - quit 0 - of "a": - result.paramA = val.parseInt - hasA = true - of "b": - result.paramB = val.parseInt - hasB = true - else: - stdout.write USAGE - quit "Unexpected option: " & key, 2 - of cmdEnd: break - except EInvalidValue: - stdout.write USAGE - quit "Invalid value " & val & " for parameter " & key, 3 - - if hasA and hasB: - result.action = cmdParams - elif hasA or hasB: - stdout.write USAGE - quit "Error: provide both A and B to operate in param mode", 4 - - -proc parseUserInput(question: string): int = - ## Parses a line of user input, showing question to the user first. - ## - ## If the user input is an empty line quit() is called. Returns the value - ## parsed as an integer. - while true: - echo question - let input = stdin.readLine - try: - result = input.parseInt - break - except EInvalidValue: - if input.len < 1: quit "Blank line detected, quitting.", 0 - echo "Sorry, `$1' doesn't seem to be a valid integer" % input - -proc interactiveMode() = - ## Asks the user for two integer values, adds them and exits. - let - paramA = parseUserInput("Enter the first parameter (blank to exit):") - paramB = parseUserInput("Enter the second parameter (blank to exit):") - echo "Calculating... $1 + $2 = $3" % [$paramA, $paramB, - $backend.myAdd(paramA, paramB)] - - -when isMainModule: - ## Main entry point. - let opt = parseCmdLine() - if cmdParams == opt.action: - echo "Param mode: $1 + $2 = $3" % [$opt.paramA, $opt.paramB, - $backend.myAdd(opt.paramA, opt.paramB)] - else: - echo "Entering interactive addition mode" - interactiveMode() diff --git a/examples/cross_calculator/nimrod_commandline/nimrod.cfg b/examples/cross_calculator/nimrod_commandline/nimrod.cfg deleted file mode 100644 index c1aedcf6a..000000000 --- a/examples/cross_calculator/nimrod_commandline/nimrod.cfg +++ /dev/null @@ -1,4 +0,0 @@ -# Nimrod configuration file. -# The file is used only to add the path of the backend to the compiler options. - -path="../nimrod_backend" diff --git a/examples/cross_calculator/nimrod_commandline/readme.txt b/examples/cross_calculator/nimrod_commandline/readme.txt deleted file mode 100644 index 5430e7b47..000000000 --- a/examples/cross_calculator/nimrod_commandline/readme.txt +++ /dev/null @@ -1,10 +0,0 @@ -In this directory you will find the nimrod commandline version of the -cross-calculator sample. - -The commandline interface can be used non interactively through switches, or -interactively when running the command without parameters. - -Compilation is fairly easy despite having the source split in different -directories. Thanks to the nimrod.cfg file, which adds the ../nimrod_backend -directory as a search path, you can compile and run the example just fine from -the command line with 'nimrod c -r nimcalculator.nim'. -- cgit 1.4.1-2-gfad0 From d19a4ca827ab04edc5b497b73132e3f40369e1a8 Mon Sep 17 00:00:00 2001 From: def Date: Sun, 15 Feb 2015 00:05:16 +0100 Subject: Fix cross_todo example --- examples/cross_todo/nim_backend/backend.nim | 219 ++++++++++++++ examples/cross_todo/nim_backend/readme.txt | 14 + examples/cross_todo/nim_backend/testbackend.nim | 83 ++++++ examples/cross_todo/nim_commandline/nim.cfg | 4 + examples/cross_todo/nim_commandline/nimtodo.nim | 327 +++++++++++++++++++++ examples/cross_todo/nim_commandline/readme.txt | 19 ++ examples/cross_todo/nimrod_backend/backend.nim | 219 -------------- examples/cross_todo/nimrod_backend/readme.txt | 14 - examples/cross_todo/nimrod_backend/testbackend.nim | 83 ------ examples/cross_todo/nimrod_commandline/nimrod.cfg | 4 - examples/cross_todo/nimrod_commandline/nimtodo.nim | 327 --------------------- examples/cross_todo/nimrod_commandline/readme.txt | 19 -- 12 files changed, 666 insertions(+), 666 deletions(-) create mode 100644 examples/cross_todo/nim_backend/backend.nim create mode 100644 examples/cross_todo/nim_backend/readme.txt create mode 100644 examples/cross_todo/nim_backend/testbackend.nim create mode 100644 examples/cross_todo/nim_commandline/nim.cfg create mode 100644 examples/cross_todo/nim_commandline/nimtodo.nim create mode 100644 examples/cross_todo/nim_commandline/readme.txt delete mode 100644 examples/cross_todo/nimrod_backend/backend.nim delete mode 100644 examples/cross_todo/nimrod_backend/readme.txt delete mode 100644 examples/cross_todo/nimrod_backend/testbackend.nim delete mode 100644 examples/cross_todo/nimrod_commandline/nimrod.cfg delete mode 100644 examples/cross_todo/nimrod_commandline/nimtodo.nim delete mode 100644 examples/cross_todo/nimrod_commandline/readme.txt diff --git a/examples/cross_todo/nim_backend/backend.nim b/examples/cross_todo/nim_backend/backend.nim new file mode 100644 index 000000000..9c7d2bafa --- /dev/null +++ b/examples/cross_todo/nim_backend/backend.nim @@ -0,0 +1,219 @@ +# Backend for a simple todo program with sqlite persistence. +# +# Most procs dealing with a TDbConn object may raise an EDb exception. + +import db_sqlite, parseutils, strutils, times + + +type + TTodo* = object + ## A todo object holding the information serialized to the database. + id: int64 ## Unique identifier of the object in the + ## database, use the getId() accessor to read it. + text*: string ## Description of the task to do. + priority*: int ## The priority can be any user defined integer. + isDone*: bool ## Done todos are still kept marked. + modificationDate: Time ## The modification time can't be modified from + ## outside of this module, use the + ## getModificationDate accessor. + + TPagedParams* = object + ## Contains parameters for a query, initialize default values with + ## initDefaults(). + pageSize*: int64 ## Lines per returned query page, -1 for + ## unlimited. + priorityAscending*: bool ## Sort results by ascending priority. + dateAscending*: bool ## Sort results by ascending modification date. + showUnchecked*: bool ## Get unchecked objects. + showChecked*: bool ## Get checked objects. + + +# - General procs +# +proc initDefaults*(params: var TPagedParams) = + ## Sets sane defaults for a TPagedParams object. + ## + ## Note that you should always provide a non zero pageSize, either a specific + ## positive value or negative for unbounded query results. + params.pageSize = high(int64) + params.priorityAscending = false + params.dateAscending = false + params.showUnchecked = true + params.showChecked = false + + +proc openDatabase*(path: string): TDbConn = + ## Creates or opens the sqlite3 database. + ## + ## Pass the path to the sqlite database, if the database doesn't exist it + ## will be created. The proc may raise a EDB exception + let + conn = db_sqlite.open(path, "user", "pass", "db") + query = sql"""CREATE TABLE IF NOT EXISTS Todos ( + id INTEGER PRIMARY KEY, + priority INTEGER NOT NULL, + is_done BOOLEAN NOT NULL, + desc TEXT NOT NULL, + modification_date INTEGER NOT NULL, + CONSTRAINT Todos UNIQUE (id))""" + + db_sqlite.exec(conn, query) + result = conn + + +# - Procs related to TTodo objects +# +proc initFromDB(id: int64; text: string; priority: int, isDone: bool; + modificationDate: Time): TTodo = + ## Returns an initialized TTodo object created from database parameters. + ## + ## The proc assumes all values are right. Note this proc is NOT exported. + assert(id >= 0, "Identity identifiers should not be negative") + result.id = id + result.text = text + result.priority = priority + result.isDone = isDone + result.modificationDate = modificationDate + + +proc getId*(todo: TTodo): int64 = + ## Accessor returning the value of the private id property. + return todo.id + + +proc getModificationDate*(todo: TTodo): Time = + ## Returns the last modification date of a TTodo entry. + return todo.modificationDate + + +proc update*(todo: var TTodo; conn: TDbConn): bool = + ## Checks the database for the object and refreshes its variables. + ## + ## Use this method if you (or another entity) have modified the database and + ## want to update the object you have with whatever the database has stored. + ## Returns true if the update suceeded, or false if the object was not found + ## in the database any more, in which case you should probably get rid of the + ## TTodo object. + assert(todo.id >= 0, "The identifier of the todo entry can't be negative") + let query = sql"""SELECT desc, priority, is_done, modification_date + FROM Todos WHERE id = ?""" + + try: + let rows = conn.getAllRows(query, $todo.id) + if len(rows) < 1: + return + assert(1 == len(rows), "Woah, didn't expect so many rows") + todo.text = rows[0][0] + todo.priority = rows[0][1].parseInt + todo.isDone = rows[0][2].parseBool + todo.modificationDate = Time(rows[0][3].parseInt) + result = true + except: + echo("Something went wrong selecting for id " & $todo.id) + + +proc save*(todo: var TTodo; conn: TDbConn): bool = + ## Saves the current state of text, priority and isDone to the database. + ## + ## Returns true if the database object was updated (in which case the + ## modification date will have changed). The proc can return false if the + ## object wasn't found, for instance, in which case you should drop that + ## object anyway and create a new one with addTodo(). Also EDb can be raised. + assert(todo.id >= 0, "The identifier of the todo entry can't be negative") + let + currentDate = getTime() + query = sql"""UPDATE Todos + SET desc = ?, priority = ?, is_done = ?, modification_date = ? + WHERE id = ?""" + rowsUpdated = conn.execAffectedRows(query, $todo.text, + $todo.priority, $todo.isDone, $int(currentDate), $todo.id) + + if 1 == rowsUpdated: + todo.modificationDate = currentDate + result = true + + +# - Procs dealing directly with the database +# +proc addTodo*(conn: TDbConn; priority: int; text: string): TTodo = + ## Inserts a new todo into the database. + ## + ## Returns the generated todo object. If there is an error EDb will be raised. + let + currentDate = getTime() + query = sql"""INSERT INTO Todos + (priority, is_done, desc, modification_date) + VALUES (?, 'false', ?, ?)""" + todoId = conn.insertId(query, priority, text, $int(currentDate)) + + result = initFromDB(todoId, text, priority, false, currentDate) + + +proc deleteTodo*(conn: TDbConn; todoId: int64): int64 {.discardable.} = + ## Deletes the specified todo identifier. + ## + ## Returns the number of rows which were affected (1 or 0) + let query = sql"""DELETE FROM Todos WHERE id = ?""" + result = conn.execAffectedRows(query, $todoId) + + +proc getNumEntries*(conn: TDbConn): int = + ## Returns the number of entries in the Todos table. + ## + ## If the function succeeds, returns the zero or positive value, if something + ## goes wrong a negative value is returned. + let query = sql"""SELECT COUNT(id) FROM Todos""" + try: + let row = conn.getRow(query) + result = row[0].parseInt + except: + echo("Something went wrong retrieving number of Todos entries") + result = -1 + + +proc getPagedTodos*(conn: TDbConn; params: TPagedParams; + page = 0'i64): seq[TTodo] = + ## Returns the todo entries for a specific page. + ## + ## Pages are calculated based on the params.pageSize parameter, which can be + ## set to a negative value to specify no limit at all. The query will be + ## affected by the TPagedParams, which should have sane values (call + ## initDefaults). + assert(page >= 0, "You should request a page zero or bigger than zero") + result = @[] + + # Well, if you don't want to see anything, there's no point in asking the db. + if not params.showUnchecked and not params.showChecked: return + + let + order_by = [ + if params.priorityAscending: "ASC" else: "DESC", + if params.dateAscending: "ASC" else: "DESC"] + + query = sql("""SELECT id, desc, priority, is_done, modification_date + FROM Todos + WHERE is_done = ? OR is_done = ? + ORDER BY priority $1, modification_date $2, id DESC + LIMIT ? * ?,?""" % order_by) + + args = @[$params.showChecked, $(not params.showUnchecked), + $params.pageSize, $page, $params.pageSize] + + #echo("Query " & string(query)) + #echo("args: " & args.join(", ")) + + var newId: BiggestInt + for row in conn.fastRows(query, args): + let numChars = row[0].parseBiggestInt(newId) + assert(numChars > 0, "Huh, couldn't parse identifier from database?") + result.add(initFromDB(int64(newId), row[1], row[2].parseInt, + row[3].parseBool, Time(row[4].parseInt))) + + +proc getTodo*(conn: TDbConn; todoId: int64): ref TTodo = + ## Returns a reference to a TTodo or nil if the todo could not be found. + var tempTodo: TTodo + tempTodo.id = todoId + if tempTodo.update(conn): + new(result) + result[] = tempTodo diff --git a/examples/cross_todo/nim_backend/readme.txt b/examples/cross_todo/nim_backend/readme.txt new file mode 100644 index 000000000..16cb592fc --- /dev/null +++ b/examples/cross_todo/nim_backend/readme.txt @@ -0,0 +1,14 @@ +This directory contains the nim backend code for the todo cross platform +example. + +Unlike the cross platform calculator example, this backend features more code, +using an sqlite database for storage. Also a basic test module is provided, not +to be included with the final program but to test the exported functionality. +The test is not embedded directly in the backend.nim file to avoid being able +to access internal data types and procs not exported and replicate the +environment of client code. + +In a bigger project with several people you could run `nim doc backend.nim` +(or use the doc2 command for a whole project) and provide the generated html +documentation to another programer for her to implement an interface without +having to look at the source code. diff --git a/examples/cross_todo/nim_backend/testbackend.nim b/examples/cross_todo/nim_backend/testbackend.nim new file mode 100644 index 000000000..131dda1cf --- /dev/null +++ b/examples/cross_todo/nim_backend/testbackend.nim @@ -0,0 +1,83 @@ +# Tests the backend code. + +import backend, db_sqlite, strutils, times + + +proc showPagedResults(conn: TDbConn; params: TPagedParams) = + ## Shows the contents of the database in pages of specified size. + ## + ## Hmm... I guess this is more of a debug proc which should be moved outside, + ## or to a commandline interface (hint). + var + page = 0'i64 + rows = conn.getPagedTodos(params) + + while rows.len > 0: + echo("page " & $page) + for row in rows: + echo("row id:$1, text:$2, priority:$3, done:$4, date:$5" % [$row.getId, + $row.text, $row.priority, $row.isDone, + $row.getModificationDate]) + # Query the database for the next page or quit. + if params.pageSize > 0: + page = page + 1 + rows = conn.getPagedTodos(params, page) + else: + break + + +proc dumTest() = + let conn = openDatabase("todo.sqlite3") + try: + let numTodos = conn.getNumEntries + echo("Current database contains " & $numTodos & " todo items.") + if numTodos < 10: + # Fill some dummy rows if there are not many entries yet. + discard conn.addTodo(3, "Filler1") + discard conn.addTodo(4, "Filler2") + + var todo = conn.addTodo(2, "Testing") + echo("New todo added with id " & $todo.getId) + + # Try changing it and updating the database. + var clonedTodo = conn.getTodo(todo.getId)[] + assert(clonedTodo.text == todo.text, "Should be equal") + todo.text = "Updated!" + todo.priority = 7 + todo.isDone = true + if todo.save(conn): + echo("Updated priority $1, done $2" % [$todo.priority, $todo.isDone]) + else: + assert(false, "Uh oh, I wasn't expecting that!") + + # Verify our cloned copy is different but can be updated. + assert(clonedTodo.text != todo.text, "Should be different") + discard clonedTodo.update(conn) + assert(clonedTodo.text == todo.text, "Should be equal") + + var params: TPagedParams + params.initDefaults + conn.showPagedResults(params) + conn.deleteTodo(todo.getId) + echo("Deleted rows for id 3? ") + let res = conn.deleteTodo(todo.getId) + echo("Deleted rows for id 3? " & $res) + if todo.update(conn): + echo("Later priority $1, done $2" % [$todo.priority, $todo.isDone]) + else: + echo("Can't update object $1 from db!" % $todo.getId) + + # Try to list content in a different way. + params.pageSize = 5 + params.priorityAscending = true + params.dateAscending = true + params.showChecked = true + conn.showPagedResults(params) + finally: + conn.close + echo("Database closed") + + +# Code that will be run only on the commandline. +when isMainModule: + dumTest() diff --git a/examples/cross_todo/nim_commandline/nim.cfg b/examples/cross_todo/nim_commandline/nim.cfg new file mode 100644 index 000000000..41c034430 --- /dev/null +++ b/examples/cross_todo/nim_commandline/nim.cfg @@ -0,0 +1,4 @@ +# Nimrod configuration file. +# The file is used only to add the path of the backend to the compiler options. + +path="../nim_backend" diff --git a/examples/cross_todo/nim_commandline/nimtodo.nim b/examples/cross_todo/nim_commandline/nimtodo.nim new file mode 100644 index 000000000..4ab17e7a2 --- /dev/null +++ b/examples/cross_todo/nim_commandline/nimtodo.nim @@ -0,0 +1,327 @@ +# Implements a command line interface against the backend. + +import backend, db_sqlite, os, parseopt, parseutils, strutils, times + +const + USAGE = """nimtodo - Nimrod cross platform todo manager + +Usage: + nimtodo [command] [list options] + +Commands: + -a=int text Adds a todo entry with the specified priority and text. + -c=int Marks the specified todo entry as done. + -u=int Marks the specified todo entry as not done. + -d=int|all Deletes a single entry from the database, or all entries. + -g Generates some rows with values for testing. + -l Lists the contents of the database. + -h, --help shows this help + +List options (optional): + -p=+|- Sorts list by ascending|desdencing priority. Default:desdencing. + -m=+|- Sorts list by ascending|desdencing date. Default:desdencing. + -t Show checked entries. By default they are not shown. + -z Hide unchecked entries. By default they are shown. + +Examples: + nimtodo -a=4 Water the plants + nimtodo -c:87 + nimtodo -d:2 + nimtodo -d:all + nimtodo -l -p=+ -m=- -t + +""" + +type + TCommand = enum # The possible types of commands + cmdAdd # The user wants to add a new todo entry. + cmdCheck # User wants to check a todo entry. + cmdUncheck # User wants to uncheck a todo entry. + cmdDelete # User wants to delete a single todo entry. + cmdNuke # User wants to purge all database entries. + cmdGenerate # Add random rows to the database, for testing. + cmdList # User wants to list contents. + + TParamConfig = object + # Structure containing the parsed options from the commandline. + command: TCommand # Store the type of operation + addPriority: int # Only valid with cmdAdd, stores priority. + addText: seq[string] # Only valid with cmdAdd, stores todo text. + todoId: int64 # The todo id for operations like check or delete. + listParams: TPagedParams # Uses the backend structure directly for params. + + +proc initDefaults(params: var TParamConfig) = + ## Initialises defaults value in the structure. + ## + ## Most importantly we want to have an empty list for addText. + params.listParams.initDefaults + params.addText = @[] + + +proc abort(message: string, value: int) = + # Simple wrapper to abort also displaying the help to the user. + stdout.write(USAGE) + quit(message, value) + + +template parseTodoIdAndSetCommand(newCommand: TCommand): stmt = + ## Helper to parse a big todo identifier into todoId and set command. + try: + let numChars = val.parseBiggestInt(newId) + if numChars < 1: raise newException(ValueError, "Empty string?") + result.command = newCommand + result.todoId = newId + except OverflowError: + raise newException(ValueError, "Value $1 too big" % val) + + +template verifySingleCommand(actions: stmt): stmt = + ## Helper to make sure only one command has been specified so far. + if specifiedCommand: + abort("Only one command can be specified at a time! (extra:$1)" % [key], 2) + else: + actions + specifiedCommand = true + + +proc parsePlusMinus(val: string, debugText: string): bool = + ## Helper to process a plus or minus character from the commandline. + ## + ## Pass the string to parse and the type of parameter for debug errors. + ## The processed parameter will be returned as true for a '+' and false for a + ## '-'. The proc aborts with a debug message if the passed parameter doesn't + ## contain one of those values. + case val + of "+": + return true + of "-": + return false + else: + abort("$1 parameter should be + or - but was '$2'." % [debugText, val], 4) + + +proc parseCmdLine(): TParamConfig = + ## Parses the commandline. + ## + ## Returns a TParamConfig structure filled with the proper values or directly + ## calls quit() with the appropriate error message. + var + specifiedCommand = false + usesListParams = false + p = initOptParser() + key, val: TaintedString + newId: BiggestInt + + result.initDefaults + + try: + while true: + next(p) + key = p.key + val = p.val + + case p.kind + of cmdArgument: + if specifiedCommand and cmdAdd == result.command: + result.addText.add(key) + else: + abort("Argument ($1) detected without add command." % [key], 1) + of cmdLongOption, cmdShortOption: + case normalize(key) + of "help", "h": + stdout.write(USAGE) + quit(0) + of "a": + verifySingleCommand: + result.command = cmdAdd + result.addPriority = val.parseInt + of "c": + verifySingleCommand: + parseTodoIdAndSetCommand(cmdCheck) + of "u": + verifySingleCommand: + parseTodoIdAndSetCommand cmdUncheck + of "d": + verifySingleCommand: + if "all" == val: + result.command = cmdNuke + else: + parseTodoIdAndSetCommand cmdDelete + of "g": + verifySingleCommand: + if val.len > 0: + abort("Unexpected value '$1' for switch l." % [val], 3) + result.command = cmdGenerate + of "l": + verifySingleCommand: + if val.len > 0: + abort("Unexpected value '$1' for switch l." % [val], 3) + result.command = cmdList + of "p": + usesListParams = true + result.listParams.priorityAscending = parsePlusMinus(val, "Priority") + of "m": + usesListParams = true + result.listParams.dateAscending = parsePlusMinus(val, "Date") + of "t": + usesListParams = true + if val.len > 0: + abort("Unexpected value '$1' for switch t." % [val], 5) + result.listParams.showChecked = true + of "z": + usesListParams = true + if val.len > 0: + abort("Unexpected value '$1' for switch z." % [val], 5) + result.listParams.showUnchecked = false + else: + abort("Unexpected option '$1'." % [key], 6) + of cmdEnd: + break + except ValueError: + abort("Invalid integer value '$1' for parameter '$2'." % [val, key], 7) + + if not specifiedCommand: + abort("Didn't specify any command.", 8) + + if cmdAdd == result.command and result.addText.len < 1: + abort("Used the add command, but provided no text/description.", 9) + + if usesListParams and cmdList != result.command: + abort("Used list options, but didn't specify the list command.", 10) + + +proc generateDatabaseRows(conn: TDbConn) = + ## Adds some rows to the database ignoring errors. + discard conn.addTodo(1, "Watch another random youtube video") + discard conn.addTodo(2, "Train some starcraft moves for the league") + discard conn.addTodo(3, "Spread the word about Nimrod") + discard conn.addTodo(4, "Give fruit superavit to neighbours") + var todo = conn.addTodo(4, "Send tax form through snail mail") + todo.isDone = true + discard todo.save(conn) + discard conn.addTodo(1, "Download new anime to watch") + todo = conn.addTodo(2, "Build train model from scraps") + todo.isDone = true + discard todo.save(conn) + discard conn.addTodo(5, "Buy latest Britney Spears album") + discard conn.addTodo(6, "Learn a functional programming language") + echo("Generated some entries, they were added to your database.") + + +proc listDatabaseContents(conn: TDbConn; listParams: TPagedParams) = + ## Dumps the database contents formatted to the standard output. + ## + ## Pass the list/filter parameters parsed from the commandline. + var params = listParams + params.pageSize = -1 + + let todos = conn.getPagedTodos(params) + if todos.len < 1: + echo("Database empty") + return + + echo("Todo id, is done, priority, last modification date, text:") + # First detect how long should be our columns for formatting. + var cols: array[0..2, int] + for todo in todos: + cols[0] = max(cols[0], ($todo.getId).len) + cols[1] = max(cols[1], ($todo.priority).len) + cols[2] = max(cols[2], ($todo.getModificationDate).len) + + # Now dump all the rows using the calculated alignment sizes. + for todo in todos: + echo("$1 $2 $3, $4, $5" % [ + ($todo.getId).align(cols[0]), + if todo.isDone: "[X]" else: "[-]", + ($todo.priority).align(cols[1]), + ($todo.getModificationDate).align(cols[2]), + todo.text]) + + +proc deleteOneTodo(conn: TDbConn; todoId: int64) = + ## Deletes a single todo entry from the database. + let numDeleted = conn.deleteTodo(todoId) + if numDeleted > 0: + echo("Deleted todo id " & $todoId) + else: + quit("Couldn't delete todo id " & $todoId, 11) + + +proc deleteAllTodos(conn: TDbConn) = + ## Deletes all the contents from the database. + ## + ## Note that it would be more optimal to issue a direct DELETE sql statement + ## on the database, but for the sake of the example we will restrict + ## ourselfves to the API exported by backend. + var + counter: int64 + params: TPagedParams + + params.initDefaults + params.pageSize = -1 + params.showUnchecked = true + params.showChecked = true + + let todos = conn.getPagedTodos(params) + for todo in todos: + if conn.deleteTodo(todo.getId) > 0: + counter += 1 + else: + quit("Couldn't delete todo id " & $todo.getId, 12) + + echo("Deleted $1 todo entries from database." % $counter) + + +proc setTodoCheck(conn: TDbConn; todoId: int64; value: bool) = + ## Changes the check state of a todo entry to the specified value. + let + newState = if value: "checked" else: "unchecked" + todo = conn.getTodo(todoId) + + if todo == nil: + quit("Can't modify todo id $1, its not in the database." % $todoId, 13) + + if todo[].isDone == value: + echo("Todo id $1 was already set to $2." % [$todoId, newState]) + return + + todo[].isDone = value + if todo[].save(conn): + echo("Todo id $1 set to $2." % [$todoId, newState]) + else: + quit("Error updating todo id $1 to $2." % [$todoId, newState]) + + +proc addTodo(conn: TDbConn; priority: int; tokens: seq[string]) = + ## Adds to the database a todo with the specified priority. + ## + ## The tokens are joined as a single string using the space character. The + ## created id will be displayed to the user. + let todo = conn.addTodo(priority, tokens.join(" ")) + echo("Created todo entry with id:$1 for priority $2 and text '$3'." % [ + $todo.getId, $todo.priority, todo.text]) + + +when isMainModule: + ## Main entry point. + let + opt = parseCmdLine() + dbPath = getConfigDir() / "nimtodo.sqlite3" + + if not dbPath.existsFile: + createDir(getConfigDir()) + echo("No database found at $1, it will be created for you." % dbPath) + + let conn = openDatabase(dbPath) + try: + case opt.command + of cmdAdd: addTodo(conn, opt.addPriority, opt.addText) + of cmdCheck: setTodoCheck(conn, opt.todoId, true) + of cmdUncheck: setTodoCheck(conn, opt.todoId, false) + of cmdDelete: deleteOneTodo(conn, opt.todoId) + of cmdNuke: deleteAllTodos(conn) + of cmdGenerate: generateDatabaseRows(conn) + of cmdList: listDatabaseContents(conn, opt.listParams) + finally: + conn.close diff --git a/examples/cross_todo/nim_commandline/readme.txt b/examples/cross_todo/nim_commandline/readme.txt new file mode 100644 index 000000000..ca4b67521 --- /dev/null +++ b/examples/cross_todo/nim_commandline/readme.txt @@ -0,0 +1,19 @@ +This directory contains the Nim commandline version of the todo cross +platform example. + +The commandline interface can be used only through switches, running the binary +once will spit out the basic help. The commands you can use are the typical on +such an application: add, check/uncheck and delete (further could be added, +like modification at expense of parsing/option complexity). The list command is +the only one which dumps the contents of the database. The output can be +filtered and sorted through additional parameters. + +When you run the program for the first time the todo database will be generated +in your user's data directory. To cope with an empty database, a special +generation switch can be used to fill the database with some basic todo entries +you can play with. + +Compilation is fairly easy despite having the source split in different +directories. Thanks to the Nim.cfg file, which adds the ../Nim_backend +directory as a search path, you can compile and run the example just fine from +the command line with 'nim c -r nimtodo.nim'. diff --git a/examples/cross_todo/nimrod_backend/backend.nim b/examples/cross_todo/nimrod_backend/backend.nim deleted file mode 100644 index 89e7d0b7e..000000000 --- a/examples/cross_todo/nimrod_backend/backend.nim +++ /dev/null @@ -1,219 +0,0 @@ -# Backend for a simple todo program with sqlite persistence. -# -# Most procs dealing with a TDbConn object may raise an EDb exception. - -import db_sqlite, parseutils, strutils, times - - -type - TTodo* = object - ## A todo object holding the information serialized to the database. - id: int64 ## Unique identifier of the object in the - ## database, use the getId() accessor to read it. - text*: string ## Description of the task to do. - priority*: int ## The priority can be any user defined integer. - isDone*: bool ## Done todos are still kept marked. - modificationDate: TTime ## The modification time can't be modified from - ## outside of this module, use the - ## getModificationDate accessor. - - TPagedParams* = object - ## Contains parameters for a query, initialize default values with - ## initDefaults(). - pageSize*: int64 ## Lines per returned query page, -1 for - ## unlimited. - priorityAscending*: bool ## Sort results by ascending priority. - dateAscending*: bool ## Sort results by ascending modification date. - showUnchecked*: bool ## Get unchecked objects. - showChecked*: bool ## Get checked objects. - - -# - General procs -# -proc initDefaults*(params: var TPagedParams) = - ## Sets sane defaults for a TPagedParams object. - ## - ## Note that you should always provide a non zero pageSize, either a specific - ## positive value or negative for unbounded query results. - params.pageSize = high(int64) - params.priorityAscending = false - params.dateAscending = false - params.showUnchecked = true - params.showChecked = false - - -proc openDatabase*(path: string): TDbConn = - ## Creates or opens the sqlite3 database. - ## - ## Pass the path to the sqlite database, if the database doesn't exist it - ## will be created. The proc may raise a EDB exception - let - conn = db_sqlite.open(path, "user", "pass", "db") - query = sql"""CREATE TABLE IF NOT EXISTS Todos ( - id INTEGER PRIMARY KEY, - priority INTEGER NOT NULL, - is_done BOOLEAN NOT NULL, - desc TEXT NOT NULL, - modification_date INTEGER NOT NULL, - CONSTRAINT Todos UNIQUE (id))""" - - db_sqlite.exec(conn, query) - result = conn - - -# - Procs related to TTodo objects -# -proc initFromDB(id: int64; text: string; priority: int, isDone: bool; - modificationDate: TTime): TTodo = - ## Returns an initialized TTodo object created from database parameters. - ## - ## The proc assumes all values are right. Note this proc is NOT exported. - assert(id >= 0, "Identity identifiers should not be negative") - result.id = id - result.text = text - result.priority = priority - result.isDone = isDone - result.modificationDate = modificationDate - - -proc getId*(todo: TTodo): int64 = - ## Accessor returning the value of the private id property. - return todo.id - - -proc getModificationDate*(todo: TTodo): TTime = - ## Returns the last modification date of a TTodo entry. - return todo.modificationDate - - -proc update*(todo: var TTodo; conn: TDbConn): bool = - ## Checks the database for the object and refreshes its variables. - ## - ## Use this method if you (or another entity) have modified the database and - ## want to update the object you have with whatever the database has stored. - ## Returns true if the update suceeded, or false if the object was not found - ## in the database any more, in which case you should probably get rid of the - ## TTodo object. - assert(todo.id >= 0, "The identifier of the todo entry can't be negative") - let query = sql"""SELECT desc, priority, is_done, modification_date - FROM Todos WHERE id = ?""" - - try: - let rows = conn.GetAllRows(query, $todo.id) - if len(rows) < 1: - return - assert(1 == len(rows), "Woah, didn't expect so many rows") - todo.text = rows[0][0] - todo.priority = rows[0][1].parseInt - todo.isDone = rows[0][2].parseBool - todo.modificationDate = TTime(rows[0][3].parseInt) - result = true - except: - echo("Something went wrong selecting for id " & $todo.id) - - -proc save*(todo: var TTodo; conn: TDbConn): bool = - ## Saves the current state of text, priority and isDone to the database. - ## - ## Returns true if the database object was updated (in which case the - ## modification date will have changed). The proc can return false if the - ## object wasn't found, for instance, in which case you should drop that - ## object anyway and create a new one with addTodo(). Also EDb can be raised. - assert(todo.id >= 0, "The identifier of the todo entry can't be negative") - let - currentDate = getTime() - query = sql"""UPDATE Todos - SET desc = ?, priority = ?, is_done = ?, modification_date = ? - WHERE id = ?""" - rowsUpdated = conn.execAffectedRows(query, $todo.text, - $todo.priority, $todo.isDone, $int(currentDate), $todo.id) - - if 1 == rowsUpdated: - todo.modificationDate = currentDate - result = true - - -# - Procs dealing directly with the database -# -proc addTodo*(conn: TDbConn; priority: int; text: string): TTodo = - ## Inserts a new todo into the database. - ## - ## Returns the generated todo object. If there is an error EDb will be raised. - let - currentDate = getTime() - query = sql"""INSERT INTO Todos - (priority, is_done, desc, modification_date) - VALUES (?, 'false', ?, ?)""" - todoId = conn.insertId(query, priority, text, $int(currentDate)) - - result = initFromDB(todoId, text, priority, false, currentDate) - - -proc deleteTodo*(conn: TDbConn; todoId: int64): int64 {.discardable.} = - ## Deletes the specified todo identifier. - ## - ## Returns the number of rows which were affected (1 or 0) - let query = sql"""DELETE FROM Todos WHERE id = ?""" - result = conn.execAffectedRows(query, $todoId) - - -proc getNumEntries*(conn: TDbConn): int = - ## Returns the number of entries in the Todos table. - ## - ## If the function succeeds, returns the zero or positive value, if something - ## goes wrong a negative value is returned. - let query = sql"""SELECT COUNT(id) FROM Todos""" - try: - let row = conn.getRow(query) - result = row[0].parseInt - except: - echo("Something went wrong retrieving number of Todos entries") - result = -1 - - -proc getPagedTodos*(conn: TDbConn; params: TPagedParams; - page = 0'i64): seq[TTodo] = - ## Returns the todo entries for a specific page. - ## - ## Pages are calculated based on the params.pageSize parameter, which can be - ## set to a negative value to specify no limit at all. The query will be - ## affected by the TPagedParams, which should have sane values (call - ## initDefaults). - assert(page >= 0, "You should request a page zero or bigger than zero") - result = @[] - - # Well, if you don't want to see anything, there's no point in asking the db. - if not params.showUnchecked and not params.showChecked: return - - let - order_by = [ - if params.priorityAscending: "ASC" else: "DESC", - if params.dateAscending: "ASC" else: "DESC"] - - query = sql("""SELECT id, desc, priority, is_done, modification_date - FROM Todos - WHERE is_done = ? OR is_done = ? - ORDER BY priority $1, modification_date $2, id DESC - LIMIT ? * ?,?""" % order_by) - - args = @[$params.showChecked, $(not params.showUnchecked), - $params.pageSize, $page, $params.pageSize] - - #echo("Query " & string(query)) - #echo("args: " & args.join(", ")) - - var newId: biggestInt - for row in conn.fastRows(query, args): - let numChars = row[0].parseBiggestInt(newId) - assert(numChars > 0, "Huh, couldn't parse identifier from database?") - result.add(initFromDB(int64(newId), row[1], row[2].parseInt, - row[3].parseBool, TTime(row[4].parseInt))) - - -proc getTodo*(conn: TDbConn; todoId: int64): ref TTodo = - ## Returns a reference to a TTodo or nil if the todo could not be found. - var tempTodo: TTodo - tempTodo.id = todoId - if tempTodo.update(conn): - new(result) - result[] = tempTodo diff --git a/examples/cross_todo/nimrod_backend/readme.txt b/examples/cross_todo/nimrod_backend/readme.txt deleted file mode 100644 index 6529f2e67..000000000 --- a/examples/cross_todo/nimrod_backend/readme.txt +++ /dev/null @@ -1,14 +0,0 @@ -This directory contains the nimrod backend code for the todo cross platform -example. - -Unlike the cross platform calculator example, this backend features more code, -using an sqlite database for storage. Also a basic test module is provided, not -to be included with the final program but to test the exported functionality. -The test is not embedded directly in the backend.nim file to avoid being able -to access internal data types and procs not exported and replicate the -environment of client code. - -In a bigger project with several people you could run `nimrod doc backend.nim` -(or use the doc2 command for a whole project) and provide the generated html -documentation to another programer for her to implement an interface without -having to look at the source code. diff --git a/examples/cross_todo/nimrod_backend/testbackend.nim b/examples/cross_todo/nimrod_backend/testbackend.nim deleted file mode 100644 index 131dda1cf..000000000 --- a/examples/cross_todo/nimrod_backend/testbackend.nim +++ /dev/null @@ -1,83 +0,0 @@ -# Tests the backend code. - -import backend, db_sqlite, strutils, times - - -proc showPagedResults(conn: TDbConn; params: TPagedParams) = - ## Shows the contents of the database in pages of specified size. - ## - ## Hmm... I guess this is more of a debug proc which should be moved outside, - ## or to a commandline interface (hint). - var - page = 0'i64 - rows = conn.getPagedTodos(params) - - while rows.len > 0: - echo("page " & $page) - for row in rows: - echo("row id:$1, text:$2, priority:$3, done:$4, date:$5" % [$row.getId, - $row.text, $row.priority, $row.isDone, - $row.getModificationDate]) - # Query the database for the next page or quit. - if params.pageSize > 0: - page = page + 1 - rows = conn.getPagedTodos(params, page) - else: - break - - -proc dumTest() = - let conn = openDatabase("todo.sqlite3") - try: - let numTodos = conn.getNumEntries - echo("Current database contains " & $numTodos & " todo items.") - if numTodos < 10: - # Fill some dummy rows if there are not many entries yet. - discard conn.addTodo(3, "Filler1") - discard conn.addTodo(4, "Filler2") - - var todo = conn.addTodo(2, "Testing") - echo("New todo added with id " & $todo.getId) - - # Try changing it and updating the database. - var clonedTodo = conn.getTodo(todo.getId)[] - assert(clonedTodo.text == todo.text, "Should be equal") - todo.text = "Updated!" - todo.priority = 7 - todo.isDone = true - if todo.save(conn): - echo("Updated priority $1, done $2" % [$todo.priority, $todo.isDone]) - else: - assert(false, "Uh oh, I wasn't expecting that!") - - # Verify our cloned copy is different but can be updated. - assert(clonedTodo.text != todo.text, "Should be different") - discard clonedTodo.update(conn) - assert(clonedTodo.text == todo.text, "Should be equal") - - var params: TPagedParams - params.initDefaults - conn.showPagedResults(params) - conn.deleteTodo(todo.getId) - echo("Deleted rows for id 3? ") - let res = conn.deleteTodo(todo.getId) - echo("Deleted rows for id 3? " & $res) - if todo.update(conn): - echo("Later priority $1, done $2" % [$todo.priority, $todo.isDone]) - else: - echo("Can't update object $1 from db!" % $todo.getId) - - # Try to list content in a different way. - params.pageSize = 5 - params.priorityAscending = true - params.dateAscending = true - params.showChecked = true - conn.showPagedResults(params) - finally: - conn.close - echo("Database closed") - - -# Code that will be run only on the commandline. -when isMainModule: - dumTest() diff --git a/examples/cross_todo/nimrod_commandline/nimrod.cfg b/examples/cross_todo/nimrod_commandline/nimrod.cfg deleted file mode 100644 index c1aedcf6a..000000000 --- a/examples/cross_todo/nimrod_commandline/nimrod.cfg +++ /dev/null @@ -1,4 +0,0 @@ -# Nimrod configuration file. -# The file is used only to add the path of the backend to the compiler options. - -path="../nimrod_backend" diff --git a/examples/cross_todo/nimrod_commandline/nimtodo.nim b/examples/cross_todo/nimrod_commandline/nimtodo.nim deleted file mode 100644 index 1067177c3..000000000 --- a/examples/cross_todo/nimrod_commandline/nimtodo.nim +++ /dev/null @@ -1,327 +0,0 @@ -# Implements a command line interface against the backend. - -import backend, db_sqlite, os, parseopt, parseutils, strutils, times - -const - USAGE = """nimtodo - Nimrod cross platform todo manager - -Usage: - nimtodo [command] [list options] - -Commands: - -a=int text Adds a todo entry with the specified priority and text. - -c=int Marks the specified todo entry as done. - -u=int Marks the specified todo entry as not done. - -d=int|all Deletes a single entry from the database, or all entries. - -g Generates some rows with values for testing. - -l Lists the contents of the database. - -h, --help shows this help - -List options (optional): - -p=+|- Sorts list by ascending|desdencing priority. Default:desdencing. - -m=+|- Sorts list by ascending|desdencing date. Default:desdencing. - -t Show checked entries. By default they are not shown. - -z Hide unchecked entries. By default they are shown. - -Examples: - nimtodo -a=4 Water the plants - nimtodo -c:87 - nimtodo -d:2 - nimtodo -d:all - nimtodo -l -p=+ -m=- -t - -""" - -type - TCommand = enum # The possible types of commands - cmdAdd # The user wants to add a new todo entry. - cmdCheck # User wants to check a todo entry. - cmdUncheck # User wants to uncheck a todo entry. - cmdDelete # User wants to delete a single todo entry. - cmdNuke # User wants to purge all database entries. - cmdGenerate # Add random rows to the database, for testing. - cmdList # User wants to list contents. - - TParamConfig = object - # Structure containing the parsed options from the commandline. - command: TCommand # Store the type of operation - addPriority: int # Only valid with cmdAdd, stores priority. - addText: seq[string] # Only valid with cmdAdd, stores todo text. - todoId: int64 # The todo id for operations like check or delete. - listParams: TPagedParams # Uses the backend structure directly for params. - - -proc initDefaults(params: var TParamConfig) = - ## Initialises defaults value in the structure. - ## - ## Most importantly we want to have an empty list for addText. - params.listParams.initDefaults - params.addText = @[] - - -proc abort(message: string, value: int) = - # Simple wrapper to abort also displaying the help to the user. - stdout.write(USAGE) - quit(message, value) - - -template parseTodoIdAndSetCommand(newCommand: TCommand): stmt = - ## Helper to parse a big todo identifier into todoId and set command. - try: - let numChars = val.parseBiggestInt(newId) - if numChars < 1: raise newException(EInvalidValue, "Empty string?") - result.command = newCommand - result.todoId = newId - except EOverflow: - raise newException(EInvalidValue, "Value $1 too big" % val) - - -template verifySingleCommand(actions: stmt): stmt = - ## Helper to make sure only one command has been specified so far. - if specifiedCommand: - abort("Only one command can be specified at a time! (extra:$1)" % [key], 2) - else: - actions - specifiedCommand = true - - -proc parsePlusMinus(val: string, debugText: string): bool = - ## Helper to process a plus or minus character from the commandline. - ## - ## Pass the string to parse and the type of parameter for debug errors. - ## The processed parameter will be returned as true for a '+' and false for a - ## '-'. The proc aborts with a debug message if the passed parameter doesn't - ## contain one of those values. - case val - of "+": - return true - of "-": - return false - else: - abort("$1 parameter should be + or - but was '$2'." % [debugText, val], 4) - - -proc parseCmdLine(): TParamConfig = - ## Parses the commandline. - ## - ## Returns a TParamConfig structure filled with the proper values or directly - ## calls quit() with the appropriate error message. - var - specifiedCommand = false - usesListParams = false - p = initOptParser() - key, val: TaintedString - newId: biggestInt - - result.initDefaults - - try: - while true: - next(p) - key = p.key - val = p.val - - case p.kind - of cmdArgument: - if specifiedCommand and cmdAdd == result.command: - result.addText.add(key) - else: - abort("Argument ($1) detected without add command." % [key], 1) - of cmdLongOption, cmdShortOption: - case normalize(key) - of "help", "h": - stdout.write(USAGE) - quit(0) - of "a": - verifySingleCommand: - result.command = cmdAdd - result.addPriority = val.parseInt - of "c": - verifySingleCommand: - parseTodoIdAndSetCommand(cmdCheck) - of "u": - verifySingleCommand: - parseTodoIdAndSetCommand cmdUncheck - of "d": - verifySingleCommand: - if "all" == val: - result.command = cmdNuke - else: - parseTodoIdAndSetCommand cmdDelete - of "g": - verifySingleCommand: - if val.len > 0: - abort("Unexpected value '$1' for switch l." % [val], 3) - result.command = cmdGenerate - of "l": - verifySingleCommand: - if val.len > 0: - abort("Unexpected value '$1' for switch l." % [val], 3) - result.command = cmdList - of "p": - usesListParams = true - result.listParams.priorityAscending = parsePlusMinus(val, "Priority") - of "m": - usesListParams = true - result.listParams.dateAscending = parsePlusMinus(val, "Date") - of "t": - usesListParams = true - if val.len > 0: - abort("Unexpected value '$1' for switch t." % [val], 5) - result.listParams.showChecked = true - of "z": - usesListParams = true - if val.len > 0: - abort("Unexpected value '$1' for switch z." % [val], 5) - result.listParams.showUnchecked = false - else: - abort("Unexpected option '$1'." % [key], 6) - of cmdEnd: - break - except EInvalidValue: - abort("Invalid integer value '$1' for parameter '$2'." % [val, key], 7) - - if not specifiedCommand: - abort("Didn't specify any command.", 8) - - if cmdAdd == result.command and result.addText.len < 1: - abort("Used the add command, but provided no text/description.", 9) - - if usesListParams and cmdList != result.command: - abort("Used list options, but didn't specify the list command.", 10) - - -proc generateDatabaseRows(conn: TDbConn) = - ## Adds some rows to the database ignoring errors. - discard conn.addTodo(1, "Watch another random youtube video") - discard conn.addTodo(2, "Train some starcraft moves for the league") - discard conn.addTodo(3, "Spread the word about Nimrod") - discard conn.addTodo(4, "Give fruit superavit to neighbours") - var todo = conn.addTodo(4, "Send tax form through snail mail") - todo.isDone = true - discard todo.save(conn) - discard conn.addTodo(1, "Download new anime to watch") - todo = conn.addTodo(2, "Build train model from scraps") - todo.isDone = true - discard todo.save(conn) - discard conn.addTodo(5, "Buy latest Britney Spears album") - discard conn.addTodo(6, "Learn a functional programming language") - echo("Generated some entries, they were added to your database.") - - -proc listDatabaseContents(conn: TDbConn; listParams: TPagedParams) = - ## Dumps the database contents formatted to the standard output. - ## - ## Pass the list/filter parameters parsed from the commandline. - var params = listParams - params.pageSize = -1 - - let todos = conn.getPagedTodos(params) - if todos.len < 1: - echo("Database empty") - return - - echo("Todo id, is done, priority, last modification date, text:") - # First detect how long should be our columns for formatting. - var cols: array[0..2, int] - for todo in todos: - cols[0] = max(cols[0], ($todo.getId).len) - cols[1] = max(cols[1], ($todo.priority).len) - cols[2] = max(cols[2], ($todo.getModificationDate).len) - - # Now dump all the rows using the calculated alignment sizes. - for todo in todos: - echo("$1 $2 $3, $4, $5" % [ - ($todo.getId).align(cols[0]), - if todo.isDone: "[X]" else: "[-]", - ($todo.priority).align(cols[1]), - ($todo.getModificationDate).align(cols[2]), - todo.text]) - - -proc deleteOneTodo(conn: TDbConn; todoId: int64) = - ## Deletes a single todo entry from the database. - let numDeleted = conn.deleteTodo(todoId) - if numDeleted > 0: - echo("Deleted todo id " & $todoId) - else: - quit("Couldn't delete todo id " & $todoId, 11) - - -proc deleteAllTodos(conn: TDbConn) = - ## Deletes all the contents from the database. - ## - ## Note that it would be more optimal to issue a direct DELETE sql statement - ## on the database, but for the sake of the example we will restrict - ## ourselfves to the API exported by backend. - var - counter: int64 - params: TPagedParams - - params.initDefaults - params.pageSize = -1 - params.showUnchecked = true - params.showChecked = true - - let todos = conn.getPagedTodos(params) - for todo in todos: - if conn.deleteTodo(todo.getId) > 0: - counter += 1 - else: - quit("Couldn't delete todo id " & $todo.getId, 12) - - echo("Deleted $1 todo entries from database." % $counter) - - -proc setTodoCheck(conn: TDbConn; todoId: int64; value: bool) = - ## Changes the check state of a todo entry to the specified value. - let - newState = if value: "checked" else: "unchecked" - todo = conn.getTodo(todoId) - - if todo == nil: - quit("Can't modify todo id $1, its not in the database." % $todoId, 13) - - if todo[].isDone == value: - echo("Todo id $1 was already set to $2." % [$todoId, newState]) - return - - todo[].isDone = value - if todo[].save(conn): - echo("Todo id $1 set to $2." % [$todoId, newState]) - else: - quit("Error updating todo id $1 to $2." % [$todoId, newState]) - - -proc addTodo(conn: TDbConn; priority: int; tokens: seq[string]) = - ## Adds to the database a todo with the specified priority. - ## - ## The tokens are joined as a single string using the space character. The - ## created id will be displayed to the user. - let todo = conn.addTodo(priority, tokens.join(" ")) - echo("Created todo entry with id:$1 for priority $2 and text '$3'." % [ - $todo.getId, $todo.priority, todo.text]) - - -when isMainModule: - ## Main entry point. - let - opt = parseCmdLine() - dbPath = getConfigDir() / "nimtodo.sqlite3" - - if not dbPath.existsFile: - createDir(getConfigDir()) - echo("No database found at $1, it will be created for you." % dbPath) - - let conn = openDatabase(dbPath) - try: - case opt.command - of cmdAdd: addTodo(conn, opt.addPriority, opt.addText) - of cmdCheck: setTodoCheck(conn, opt.todoId, true) - of cmdUncheck: setTodoCheck(conn, opt.todoId, false) - of cmdDelete: deleteOneTodo(conn, opt.todoId) - of cmdNuke: deleteAllTodos(conn) - of cmdGenerate: generateDatabaseRows(conn) - of cmdList: listDatabaseContents(conn, opt.listParams) - finally: - conn.close diff --git a/examples/cross_todo/nimrod_commandline/readme.txt b/examples/cross_todo/nimrod_commandline/readme.txt deleted file mode 100644 index ca4b67521..000000000 --- a/examples/cross_todo/nimrod_commandline/readme.txt +++ /dev/null @@ -1,19 +0,0 @@ -This directory contains the Nim commandline version of the todo cross -platform example. - -The commandline interface can be used only through switches, running the binary -once will spit out the basic help. The commands you can use are the typical on -such an application: add, check/uncheck and delete (further could be added, -like modification at expense of parsing/option complexity). The list command is -the only one which dumps the contents of the database. The output can be -filtered and sorted through additional parameters. - -When you run the program for the first time the todo database will be generated -in your user's data directory. To cope with an empty database, a special -generation switch can be used to fill the database with some basic todo entries -you can play with. - -Compilation is fairly easy despite having the source split in different -directories. Thanks to the Nim.cfg file, which adds the ../Nim_backend -directory as a search path, you can compile and run the example just fine from -the command line with 'nim c -r nimtodo.nim'. -- cgit 1.4.1-2-gfad0 From 0a01b9143f892f60b9477e8995f2a11a2ee7734a Mon Sep 17 00:00:00 2001 From: def Date: Sun, 15 Feb 2015 00:07:34 +0100 Subject: nimrod.cfg isn't working anymore, rename to nim.cfg --- tests/manyloc/keineschweine/enet_server/nim.cfg | 9 +++++++++ tests/manyloc/keineschweine/enet_server/nimrod.cfg | 9 --------- tests/manyloc/keineschweine/server/nim.cfg | 6 ++++++ tests/manyloc/keineschweine/server/nimrod.cfg | 6 ------ 4 files changed, 15 insertions(+), 15 deletions(-) create mode 100644 tests/manyloc/keineschweine/enet_server/nim.cfg delete mode 100644 tests/manyloc/keineschweine/enet_server/nimrod.cfg create mode 100644 tests/manyloc/keineschweine/server/nim.cfg delete mode 100644 tests/manyloc/keineschweine/server/nimrod.cfg diff --git a/tests/manyloc/keineschweine/enet_server/nim.cfg b/tests/manyloc/keineschweine/enet_server/nim.cfg new file mode 100644 index 000000000..72ef47ee0 --- /dev/null +++ b/tests/manyloc/keineschweine/enet_server/nim.cfg @@ -0,0 +1,9 @@ +path = ".." +path = "../dependencies/sfml" +path = "../dependencies/enet" +path = "../dependencies/nake" +path = "../dependencies/genpacket" +path = "../lib" +define = "noChipmunk" +define = "noSFML" + diff --git a/tests/manyloc/keineschweine/enet_server/nimrod.cfg b/tests/manyloc/keineschweine/enet_server/nimrod.cfg deleted file mode 100644 index 72ef47ee0..000000000 --- a/tests/manyloc/keineschweine/enet_server/nimrod.cfg +++ /dev/null @@ -1,9 +0,0 @@ -path = ".." -path = "../dependencies/sfml" -path = "../dependencies/enet" -path = "../dependencies/nake" -path = "../dependencies/genpacket" -path = "../lib" -define = "noChipmunk" -define = "noSFML" - diff --git a/tests/manyloc/keineschweine/server/nim.cfg b/tests/manyloc/keineschweine/server/nim.cfg new file mode 100644 index 000000000..fdc45a8e1 --- /dev/null +++ b/tests/manyloc/keineschweine/server/nim.cfg @@ -0,0 +1,6 @@ +debugger = off +deadCodeElim = on +path = ".." +path = "../genpacket" +path = "../helpers" +define = NoSFML diff --git a/tests/manyloc/keineschweine/server/nimrod.cfg b/tests/manyloc/keineschweine/server/nimrod.cfg deleted file mode 100644 index fdc45a8e1..000000000 --- a/tests/manyloc/keineschweine/server/nimrod.cfg +++ /dev/null @@ -1,6 +0,0 @@ -debugger = off -deadCodeElim = on -path = ".." -path = "../genpacket" -path = "../helpers" -define = NoSFML -- cgit 1.4.1-2-gfad0 From c63c3a86543499c5d5ba3cd477e01be0be876c58 Mon Sep 17 00:00:00 2001 From: Billingsly Wetherfordshire Date: Sat, 14 Feb 2015 17:14:06 -0600 Subject: Update website.ini generate docs for basic2d,basic3d --- web/website.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/web/website.ini b/web/website.ini index c0a648c56..6757abcd8 100644 --- a/web/website.ini +++ b/web/website.ini @@ -61,6 +61,7 @@ srcdoc2: "pure/rawsockets;pure/asynchttpserver;pure/net;pure/selectors;pure/futu srcdoc2: "pure/md5" srcdoc2: "posix/posix" srcdoc2: "pure/fenv" +srcdoc2: "pure/basic2d;pure/basic3d" ; Note: everything under 'webdoc' doesn't get listed in the index, so wrappers ; should live here -- cgit 1.4.1-2-gfad0 From e22ae986f934fa0b1700cbaede89fa762ef16477 Mon Sep 17 00:00:00 2001 From: Billingsly Wetherfordshire Date: Sat, 14 Feb 2015 17:15:30 -0600 Subject: Update basic3d.nim fix rst error --- lib/pure/basic3d.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/basic3d.nim b/lib/pure/basic3d.nim index c00764fc5..9a8e006ec 100644 --- a/lib/pure/basic3d.nim +++ b/lib/pure/basic3d.nim @@ -812,7 +812,7 @@ proc bisect*(v1,v2:TVector3d):TVector3d {.noInit.}= ## Computes the bisector between v1 and v2 as a normalized vector. ## If one of the input vectors has zero length, a normalized version ## of the other is returned. If both input vectors has zero length, - ## an arbitrary normalized vector `v1`is returned. + ## an arbitrary normalized vector `v1` is returned. var vmag1=v1.len vmag2=v2.len -- cgit 1.4.1-2-gfad0 From 706544c63cf90d62a8c26a9cb6f8f96e5b478a73 Mon Sep 17 00:00:00 2001 From: Billingsly Wetherfordshire Date: Sat, 14 Feb 2015 17:19:13 -0600 Subject: Update lib.txt add basic2d/3d to standard libraries list --- doc/lib.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/lib.txt b/doc/lib.txt index b7c94b505..6897f71e0 100644 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -189,6 +189,12 @@ Math libraries Floating-point environment. Handling of floating-point rounding and exceptions (overflow, zero-devide, etc.). +* `basic2d `_ + Basic 2d support with vectors, points, matrices and some basic utilities. + +* `basic3d `_ + Basic 3d support with vectors, points, matrices and some basic utilities. + Internet Protocols and Support ------------------------------ -- cgit 1.4.1-2-gfad0 From 8ed31ffd2b8cd0605c7db764e951b025129696bc Mon Sep 17 00:00:00 2001 From: Billingsly Wetherfordshire Date: Sat, 14 Feb 2015 17:20:21 -0600 Subject: Update lib.txt removed IRC module from mention on lib.html --- doc/lib.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/doc/lib.txt b/doc/lib.txt index 6897f71e0..da45afea6 100644 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -52,7 +52,7 @@ Core Locks and condition variables for Nim. * `macros `_ - Contains the AST API and documentation of Nim for writing macros. + Contains the AST API and documentation of Nim for writing macros.i * `typeinfo `_ Provides (unsafe) access to Nim's run time type information. @@ -224,9 +224,6 @@ Internet Protocols and Support * `smtp `_ This module implement a simple SMTP client. -* `irc `_ - This module implements an asynchronous IRC client. - * `ftpclient `_ This module implements an FTP client. -- cgit 1.4.1-2-gfad0 From 78cae0dd45f41f795a4876f9b31c058fbd3b97c1 Mon Sep 17 00:00:00 2001 From: Billingsly Wetherfordshire Date: Sat, 14 Feb 2015 17:21:20 -0600 Subject: Update lib.txt oops --- doc/lib.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/lib.txt b/doc/lib.txt index da45afea6..4185e6f74 100644 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -52,7 +52,7 @@ Core Locks and condition variables for Nim. * `macros `_ - Contains the AST API and documentation of Nim for writing macros.i + Contains the AST API and documentation of Nim for writing macros. * `typeinfo `_ Provides (unsafe) access to Nim's run time type information. -- cgit 1.4.1-2-gfad0 From a7484ac092bfa47b87fe303ce55c9b2f28e3bc70 Mon Sep 17 00:00:00 2001 From: Hans Raaf Date: Sun, 15 Feb 2015 04:44:15 +0100 Subject: Fixed non exhaustive case by adding else --- lib/pure/matchers.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/matchers.nim b/lib/pure/matchers.nim index 1188be4ca..d55963c15 100644 --- a/lib/pure/matchers.nim +++ b/lib/pure/matchers.nim @@ -42,7 +42,7 @@ proc validEmailAddress*(s: string): bool {.noSideEffect, case toLower(x) of "com", "org", "net", "gov", "mil", "biz", "info", "mobi", "name", "aero", "jobs", "museum": return true - return false + else: return false proc parseInt*(s: string, value: var int, validRange: Slice[int]) {. noSideEffect, rtl, extern: "nmatchParseInt".} = -- cgit 1.4.1-2-gfad0