summary refs log tree commit diff stats
path: root/examples
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2012-11-01 15:36:08 -0700
committerAraq <rumpf_a@web.de>2012-11-01 15:36:08 -0700
commit12f7a0b0cab8ed4a30c6914804068780f34af848 (patch)
tree09cd4f2ffa02c1b6fd199c0e7d89a6d987a72939 /examples
parent9ef0c5359704b133f461bebd347c1403be834260 (diff)
parenta8df766fd9bbdfcf7c7549bdc53a6e43710d3d9e (diff)
downloadNim-12f7a0b0cab8ed4a30c6914804068780f34af848.tar.gz
Merge pull request #243 from gradha/pr_database_example
Pr database example
Diffstat (limited to 'examples')
-rw-r--r--examples/cross_calculator/readme.txt2
-rw-r--r--examples/cross_todo/nimrod_backend/backend.nim222
-rw-r--r--examples/cross_todo/nimrod_backend/readme.txt14
-rw-r--r--examples/cross_todo/nimrod_backend/testbackend.nim86
-rw-r--r--examples/cross_todo/nimrod_commandline/nimtodo.nim347
-rw-r--r--examples/cross_todo/nimrod_commandline/readme.txt18
-rw-r--r--examples/cross_todo/readme.txt5
7 files changed, 694 insertions, 0 deletions
diff --git a/examples/cross_calculator/readme.txt b/examples/cross_calculator/readme.txt
index e59cb7e54..55c403d0e 100644
--- a/examples/cross_calculator/readme.txt
+++ b/examples/cross_calculator/readme.txt
@@ -9,3 +9,5 @@ platforms.
 To avoid duplication of code, the backend code lies in a separate directory and

 each platform compiles it with a different custom build process, usually

 generating C code in a temporary build directory.

+

+For a more ellaborate and useful example see the cross_todo example.

diff --git a/examples/cross_todo/nimrod_backend/backend.nim b/examples/cross_todo/nimrod_backend/backend.nim
new file mode 100644
index 000000000..190ecf535
--- /dev/null
+++ b/examples/cross_todo/nimrod_backend/backend.nim
@@ -0,0 +1,222 @@
+# Backend for a simple todo program with sqlite persistence.
+#
+# Most procs dealing with a TDbConn object may raise an EDb exception.
+
+import db_sqlite
+import parseutils
+import strutils
+import times
+
+
+type
+  TTodo* = object of TObject
+    ## 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 of TObject
+    ## 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
new file mode 100644
index 000000000..6529f2e67
--- /dev/null
+++ b/examples/cross_todo/nimrod_backend/readme.txt
@@ -0,0 +1,14 @@
+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
new file mode 100644
index 000000000..d46b34380
--- /dev/null
+++ b/examples/cross_todo/nimrod_backend/testbackend.nim
@@ -0,0 +1,86 @@
+# Tests the backend code.
+
+import backend
+import db_sqlite
+import strutils
+import 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/nimtodo.nim b/examples/cross_todo/nimrod_commandline/nimtodo.nim
new file mode 100644
index 000000000..83405a4a0
--- /dev/null
+++ b/examples/cross_todo/nimrod_commandline/nimtodo.nim
@@ -0,0 +1,347 @@
+# Implements a command line interface against the backend.
+
+import backend
+import db_sqlite
+import os
+import parseopt
+import parseutils
+import strutils
+import 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
+    commandAdd          # The user wants to add a new todo entry.
+    commandCheck        # User wants to check a todo entry.
+    commandUncheck      # User wants to uncheck a todo entry.
+    commandDelete       # User wants to delete a single todo entry.
+    commandNuke         # User wants to purge all database entries.
+    commandGenerate     # Add random rows to the database, for testing.
+    commandList         # User wants to list contents.
+
+  TParamConfig = object of TObject
+    # Structure containing the parsed options from the commandline.
+    command: TCommand         # Store the type of operation
+    addPriority: int          # Only valid with commandAdd, stores priority.
+    addText: seq[string]      # Only valid with commandAdd, 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 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 commandAdd == result.command:
+          result.addText.add(key)
+        else:
+          stdout.write(USAGE)
+          quit("Argument ($1) detected without add command." % [key], 1)
+      of cmdLongOption, cmdShortOption:
+        case normalize(key)
+        of "help", "h":
+          stdout.write(USAGE)
+          quit(0)
+        of "a":
+          if specifiedCommand:
+            stdout.write(USAGE)
+            quit("Only one command can be specified at a time! ($1)" % [val], 2)
+          else:
+            result.command = commandAdd
+            result.addPriority = val.parseInt
+            specifiedCommand = true
+        of "c":
+          if specifiedCommand:
+            stdout.write(USAGE)
+            quit("Only one command can be specified at a time! ($1)" % [val], 2)
+          else:
+            result.command = commandCheck
+            let numChars = string(val).parseBiggestInt(newId)
+            if numChars < 1: raise newException(EInvalidValue, "Empty string?")
+            result.todoId = newId
+            specifiedCommand = true
+        of "u":
+          if specifiedCommand:
+            stdout.write(USAGE)
+            quit("Only one command can be specified at a time! ($1)" % [val], 2)
+          else:
+            result.command = commandUncheck
+            let numChars = val.parseBiggestInt(newId)
+            if numChars < 1: raise newException(EInvalidValue, "Empty string?")
+            result.todoId = newId
+            specifiedCommand = true
+        of "d":
+          if specifiedCommand:
+            stdout.write(USAGE)
+            quit("Only one command can be specified at a time! ($1)" % [val], 2)
+          else:
+            if "all" == val:
+              result.command = commandNuke
+            else:
+              result.command = commandDelete
+              let numChars = val.parseBiggestInt(newId)
+              if numChars < 1:
+                raise newException(EInvalidValue, "Empty string?")
+              result.todoId = newId
+            specifiedCommand = true
+        of "g":
+          if specifiedCommand:
+            stdout.write(USAGE)
+            quit("Only one command can be specified at a time! ($1)" % [val], 2)
+          else:
+            if val.len > 0:
+              stdout.write(USAGE)
+              quit("Unexpected value '$1' for switch l." % [val], 3)
+            result.command = commandGenerate
+            specifiedCommand = true
+        of "l":
+          if specifiedCommand:
+            stdout.write(USAGE)
+            quit("Only one command can be specified at a time! ($1)" % [val], 2)
+          else:
+            if val.len > 0:
+              stdout.write(USAGE)
+              quit("Unexpected value '$1' for switch l." % [val], 3)
+            result.command = commandList
+            specifiedCommand = true
+        of "p":
+          usesListParams = true
+          if "+" == val:
+            result.listParams.priorityAscending = true
+          elif "-" == val:
+            result.listParams.priorityAscending = false
+          else:
+            stdout.write(USAGE)
+            quit("Priority parameter ($1) should be + or |." % [val], 4)
+        of "m":
+          usesListParams = true
+          if "+" == val:
+            result.listParams.dateAscending = true
+          elif "-" == val:
+            result.listParams.dateAscending = false
+          else:
+            stdout.write(USAGE)
+            quit("Date parameter ($1) should be + or |." % [val], 4)
+        of "t":
+          usesListParams = true
+          if val.len > 0:
+            stdout.write(USAGE)
+            quit("Unexpected value '$1' for switch t." % [val], 5)
+          result.listParams.showChecked = true
+        of "z":
+          usesListParams = true
+          if val.len > 0:
+            stdout.write(USAGE)
+            quit("Unexpected value '$1' for switch z." % [val], 5)
+          result.listParams.showUnchecked = false
+        else:
+          stdout.write(USAGE)
+          quit("Unexpected option '$1'." % [key], 6)
+      of cmdEnd:
+        break
+  except EInvalidValue:
+    stdout.write(USAGE)
+    quit("Invalid int value '$1' for parameter '$2'." % [val, key], 7)
+
+  if not specifiedCommand:
+    stdout.write(USAGE)
+    quit("Didn't specify any command.", 8)
+
+  if commandAdd == result.command and result.addText.len < 1:
+    stdout.write(USAGE)
+    quit("Used the add command, but provided no text/description.", 9)
+
+  if usesListParams and commandList != result.command:
+    stdout.write(USAGE)
+    quit("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 commandAdd: addTodo(conn, opt.addPriority, opt.addText)
+    of commandCheck: setTodoCheck(conn, opt.todoId, true)
+    of commandUncheck: setTodoCheck(conn, opt.todoId, false)
+    of commandDelete: deleteOneTodo(conn, opt.todoId)
+    of commandNuke: deleteAllTodos(conn)
+    of commandGenerate: generateDatabaseRows(conn)
+    of commandList: 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
new file mode 100644
index 000000000..17820a7d3
--- /dev/null
+++ b/examples/cross_todo/nimrod_commandline/readme.txt
@@ -0,0 +1,18 @@
+This directory contains the nimrod 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 of the interface is fairly easy, just include the path to the
+backend in your compilation command. A basic build.sh is provided for unix like
+platforms with the correct parameters.
diff --git a/examples/cross_todo/readme.txt b/examples/cross_todo/readme.txt
new file mode 100644
index 000000000..acea8973a
--- /dev/null
+++ b/examples/cross_todo/readme.txt
@@ -0,0 +1,5 @@
+The cross platform todo illustrates how to use Nimrod to create a backend

+called by different native user interfaces.

+

+This example builds on the knowledge learned from the cross_calculator example.

+Check it out first to learn how to set up nimrod on different platforms.