diff options
Diffstat (limited to 'examples/cross_todo')
-rw-r--r-- | examples/cross_todo/nim_backend/backend.nim | 219 | ||||
-rw-r--r-- | examples/cross_todo/nim_backend/readme.txt | 14 | ||||
-rw-r--r-- | examples/cross_todo/nim_backend/testbackend.nim | 83 | ||||
-rw-r--r-- | examples/cross_todo/nim_commandline/nim.cfg | 4 | ||||
-rw-r--r-- | examples/cross_todo/nim_commandline/nimtodo.nim | 327 | ||||
-rw-r--r-- | examples/cross_todo/nim_commandline/readme.txt | 19 | ||||
-rw-r--r-- | examples/cross_todo/readme.txt | 5 |
7 files changed, 0 insertions, 671 deletions
diff --git a/examples/cross_todo/nim_backend/backend.nim b/examples/cross_todo/nim_backend/backend.nim deleted file mode 100644 index 6869665f8..000000000 --- a/examples/cross_todo/nim_backend/backend.nim +++ /dev/null @@ -1,219 +0,0 @@ -# Backend for a simple todo program with sqlite persistence. -# -# Most procs dealing with a DbConn 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): DbConn = - ## 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: DbConn): 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 succeeded, 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: DbConn): 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: DbConn; 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: DbConn; 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: DbConn): 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: DbConn; 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: DbConn; 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 deleted file mode 100644 index 4b31408e3..000000000 --- a/examples/cross_todo/nim_backend/readme.txt +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 6754f013a..000000000 --- a/examples/cross_todo/nim_backend/testbackend.nim +++ /dev/null @@ -1,83 +0,0 @@ -# Tests the backend code. - -import backend, db_sqlite, strutils, times - - -proc showPagedResults(conn: DbConn; 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 deleted file mode 100644 index 41c034430..000000000 --- a/examples/cross_todo/nim_commandline/nim.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="../nim_backend" diff --git a/examples/cross_todo/nim_commandline/nimtodo.nim b/examples/cross_todo/nim_commandline/nimtodo.nim deleted file mode 100644 index 339846071..000000000 --- a/examples/cross_todo/nim_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(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: DbConn) = - ## 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: DbConn; 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: DbConn; 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: DbConn) = - ## 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: DbConn; 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: DbConn; 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 deleted file mode 100644 index ca4b67521..000000000 --- a/examples/cross_todo/nim_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'. diff --git a/examples/cross_todo/readme.txt b/examples/cross_todo/readme.txt deleted file mode 100644 index 5be01e197..000000000 --- a/examples/cross_todo/readme.txt +++ /dev/null @@ -1,5 +0,0 @@ -The cross platform todo illustrates how to use Nim 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 Nim on different platforms. |