Adds command line interface for todo example.

This commit is contained in:
Grzegorz Adam Hankiewicz
2012-10-31 21:39:07 +01:00
parent c71e0a409a
commit a8df766fd9
3 changed files with 366 additions and 0 deletions

1
.gitignore vendored
View File

@@ -26,6 +26,7 @@ examples/cross_todo/nimrod_backend/*.html
examples/cross_todo/nimrod_backend/backend
examples/cross_todo/nimrod_backend/testbackend
examples/cross_todo/nimrod_backend/todo.sqlite3
examples/cross_todo/nimrod_commandline/nimtodo
# iOS specific wildcards.
*.mode1v3

View File

@@ -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

View File

@@ -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.