diff options
author | def <dennis@felsin9.de> | 2015-02-15 00:05:16 +0100 |
---|---|---|
committer | def <dennis@felsin9.de> | 2015-02-15 00:06:57 +0100 |
commit | d19a4ca827ab04edc5b497b73132e3f40369e1a8 (patch) | |
tree | 11e70d0efc327e9d53607ff8c9a2a99f458be75c /examples/cross_todo/nim_commandline | |
parent | 6244cc2e4fb7aaabd14352a21add5236220a8198 (diff) | |
download | Nim-d19a4ca827ab04edc5b497b73132e3f40369e1a8.tar.gz |
Fix cross_todo example
Diffstat (limited to 'examples/cross_todo/nim_commandline')
-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 |
3 files changed, 350 insertions, 0 deletions
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'. |