Compare commits

39 Commits

Author SHA1 Message Date
f4c71d9427 Improve update project/task 2018-07-22 00:40:12 +02:00
b072259efb TO SQ fix bad detailed output 2018-07-22 00:28:16 +02:00
a23efc330c No 2 projects with same name 2018-07-22 00:25:31 +02:00
59dcbeb101 Fix bad detailed output 2018-07-22 00:20:56 +02:00
81c7b3473e Improve deleting objects 2018-07-22 00:10:52 +02:00
a95d7ec051 Improve task deleting 2018-07-21 19:02:12 +02:00
c654a5f2eb Improve move task 2018-07-21 18:13:25 +02:00
58d5a9ef53 Improve update task 2018-07-21 18:03:08 +02:00
b5619c41eb Current value on delete + no create without parent 2018-07-21 17:42:28 +02:00
9f275c76be TO SQ Cascading 2018-07-21 17:31:04 +02:00
ecc854084d Trigger for created/updated date 2018-07-21 17:30:32 +02:00
fa25b86a2d Cascade deleting
Fix bad note deleting when no ID specified
2018-07-21 16:14:48 +02:00
f426b616fd Clean lint problems 2018-07-21 15:55:07 +02:00
b18c4dcd18 black formatting 2018-07-21 15:41:48 +02:00
5f9ea6f0b7 Use an utils module 2018-07-21 15:38:31 +02:00
438eb2c677 Pits help
Basic report of original Pit help. Need review
2018-07-21 15:26:43 +02:00
dbe6b77e31 Improve command handler + doc strstr 2018-07-20 21:31:41 +02:00
c2bc3166f2 Fix init log/action 2018-07-20 21:04:18 +02:00
59df5a03c7 Utils scripts 2018-07-13 01:13:23 +02:00
405cedb032 Init (override) + info + version 2018-07-13 01:13:21 +02:00
4dc654a68c Move main part in a function 2018-07-12 23:47:14 +02:00
9b57e1f73e Normalize action message 2018-07-11 02:02:05 +02:00
6c2b4f2c58 Use a current table + trigger 2018-07-11 01:47:29 +02:00
c0ece7a11c WIP Work on action
Very restrictive - use a 'current' table
2018-07-11 01:14:17 +02:00
d74656d9fa Notes 2018-07-10 23:43:26 +02:00
5dfc45cb7c View tasks 2018-07-10 23:23:50 +02:00
6fb1808dae Move task 2018-07-10 23:09:24 +02:00
f1bfefa7ab Small fixes 2018-07-10 23:09:16 +02:00
75fce01c3b Exit on see none existing project 2018-07-10 22:39:29 +02:00
e650affada WIP Task 2018-07-10 22:36:53 +02:00
3b5f96a0e0 move args 2018-07-10 22:36:44 +02:00
4473a11de9 Change way to set a project as active 2018-07-10 21:10:32 +02:00
c6461f2e2b Naive view project function 2018-07-10 21:10:12 +02:00
2217abb304 Improve update 2018-07-10 20:06:49 +02:00
7bb3f9af80 Complete project 2018-07-10 01:50:52 +02:00
6f110c31b5 Create note 2018-07-09 02:25:40 +02:00
b33ec432f6 Fix log command 2018-07-09 02:16:33 +02:00
a0cc2e1e02 Create task 2018-07-09 02:13:50 +02:00
f19965f1a1 WIP Try to use a personnal arg parsing system 2018-07-09 01:47:52 +02:00
12 changed files with 1365 additions and 394 deletions

152
action.py
View File

@@ -1,85 +1,157 @@
import sqlite3
import os
import argparse
import json
import getpass
import datetime
import logging
from datetime import datetime
from enum import Enum
import sys
class TypeAction(Enum):
CREATE = 'created'
UPDATE = 'updated'
MOVE = 'moved'
CREATE = "created"
UPDATE = "updated"
MOVE = "moved"
DELETE = "deleted"
class Action:
def __init__(self, row):
if not row:
logging.debug('Action - empty row')
logging.debug("Action - empty row")
# TODO
# raise Exception('No action found - database need to be initialized.')
else:
logging.debug('Action - fill fields')
logging.debug("Action - fill fields")
self.project_id, self.task_id, self.note_id = row
def __str__(self):
return str(self.__dict__)
def handle_action(args, last_action, conn):
logging.info('>> handle action')
query = 'SELECT * FROM action;'
def handle_action(_1, _2, conn):
logging.info(">> handle action")
query = "SELECT * FROM action;"
cursor = conn.cursor()
cursor.execute(query)
for row in cursor.fetchall():
log_id, project_id, task_id, note_id, username, taction, message, created_at = row
_, project_id, task_id, note_id, username, taction, message, created_at = row
logging.debug(row)
formated_date = datetime.strptime(created_at[:26], '%Y-%m-%d %H:%M:%S.%f').strftime('%b %d, %Y %H:%M')
formated_date = datetime.strptime(
created_at[:26], "%Y-%m-%d %H:%M:%S.%f"
).strftime("%b %d, %Y %H:%M")
if taction == 'init':
print('{} ({}): {}'.format(formated_date, username, message))
sys.exit(0)
if taction == "init":
print("{} ({}): {}".format(formated_date, username, message))
else:
object_type = ""
id_object = ""
object_type = ''
id_object = ''
object_type = '' # TODO Enum ?
if (project_id):
object_type = 'project'
id_object = project_id
if (task_id):
object_type = 'task'
id_object = task_id
if (note_id):
object_type = 'note'
id_object = note_id
print('{} ({}): {} {} {}: {}'.format(formated_date, username, taction, object_type, id_object, message))
object_type = "" # TODO Enum ?
if project_id:
object_type = "project"
id_object = project_id
if task_id:
object_type = "task"
id_object = task_id
if note_id:
object_type = "note"
id_object = note_id
print(
"{} ({}): {} {} {}: {}".format(
formated_date, username, taction, object_type, id_object, message
)
)
# print('Sep 02, 2017 02:33 (dakota ): updated task 1: Passage à Angular 4 (status: in progress)')
# Sep 02, 2017 02:33 (dakota ): updated task 1: Passage à Angular 4 (status: in progress)
# Jun 13, 2018 01:55 (edelweiss ): updated note 67: (message: Fix anonymize when get a replica with ID [task 30, status:in progress]
# Jul 05, 2018 00:40 (budd): moved task 2: from project 1 to project 2
def create_action(cursor, project_id = '', task_id = '', note_id = '', message = '', action = TypeAction.CREATE):
def record_action(
cursor, action_type, message, project_id="", task_id=None, note_id=None
):
query = """
INSERT INTO action (project_id, task_id, note_id, username, action, message, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?);
"""
cursor.execute(query, (project_id, task_id, note_id, getpass.getuser(), action.value, message, datetime.now(),))
cursor.execute(
query,
(
project_id,
task_id,
note_id,
getpass.getuser(),
action_type.value,
message,
datetime.now(),
),
)
logging.debug('created action')
logging.debug("created action")
def read_last_action(conn):
logging.info('> last_action')
def set_active(cursor, project_id=None, task_id=None, note_id=None):
query = "UPDATE current SET {} = ?"
if project_id:
query = query.format("project_id")
obj_id = project_id
elif task_id:
query = query.format("task_id")
obj_id = task_id
elif note_id:
query = query.format("note_id")
obj_id = note_id
cursor.execute(query, (obj_id,))
def read_current(conn):
logging.info("> last_action")
# query = 'SELECT * FROM action WHERE id = (SELECT MAX(id) FROM action'
# query = 'SELECT project_id, task_id, note_id FROM action'
query = 'SELECT project_id, task_id, note_id FROM action ORDER BY id DESC LIMIT 1;'
query = "SELECT project_id, task_id, note_id FROM current;"
cursor = conn.cursor()
cursor.execute(query)
last_action = Action(cursor.fetchone())
return last_action
return last_action
def _get_border_action(conn, last=False):
# Used for info
logging.info("> get_border_action")
# query = 'SELECT * FROM action WHERE id = (SELECT MAX(id) FROM action'
# query = 'SELECT project_id, task_id, note_id FROM action'
query = "SELECT username, created_at FROM action ORDER BY id {} LIMIT 1;"
query = query.format("DESC" if last else "")
logging.debug(query)
cursor = conn.cursor()
cursor.execute(query)
data = cursor.fetchone()
formated_date = datetime.strptime(data[1][:26], "%Y-%m-%d %H:%M:%S.%f").strftime(
"%b %d, %Y at %H:%M"
)
# TODO Extrat a format date method
return (data[0], formated_date)
def get_first_action(conn):
"""
Get first action entry and return a tuple with username and readable date
"""
return _get_border_action(conn)
def get_last_action(conn):
"""
Get last action entry and return a tuple with username and readable date
"""
return _get_border_action(conn, True)

38
args.py Normal file
View File

@@ -0,0 +1,38 @@
import sys
import logging
def get_string_arg(args, required):
"""
Take an array of element, take first element and return it stripped.
If array is empty or None, print an error message with required and exit
in error.
:param args: an array of arguments. Can be empty or None
:param required: the message to print in no args (or None)
:return: The arg stripped
"""
if not args:
print(f"Missing {required}")
sys.exit(1)
logging.debug(args)
return args[0].strip()
# TODO Move this function
def arg_string(arg, required=""):
# TODO To meld with get_string_args
if required and arg.startswith("-"):
print("required ", required)
sys.exit("1")
else:
return arg.strip()
def arg_number(arg): # , required=""):
if not arg.isdigit():
print("Invalid value")
sys.exit(1)
return int(arg)

16
init.sh Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
rm pits.db
python3.6 pits.py init
python3.6 pits.py p -c 'one'
python3.6 pits.py p -c 'two'
python3.6 pits.py t -c 'test task one'
python3.6 pits.py t -c 'test task two'
python3.6 pits.py n -c 'test note one'
python3.6 pits.py n -c 'test note two'
python3.6 pits.py p -e -s 'new status' -n 'new name'
python3.6 pits.py p -e -n 'second name'
python3.6 pits.py t -e -n 'new task name'
python3.6 pits.py t -e -n 'second name' -p 'high' -s 'wip'

2
lint.sh Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
python3.6 -m pylint --disable=C pits.py project.py task.py note.py action.py args.py

182
note.py
View File

@@ -1,39 +1,71 @@
# import sqlite3
import os
import argparse
import json
import getpass
import datetime
import logging
import sys
from action import record_action
from action import TypeAction
from args import get_string_arg
from args import arg_number
class Note:
def __init__(self):
self.message = None
import action
def handle_note(args, last_action, conn):
logging.info('> handle note')
logging.debug('args: ' + str(args))
logging.info("> handle note")
logging.debug("args: %s", args)
logging.debug('Last action: {}'.format(last_action))
if not args:
list_note(
last_action.project_id, last_action.task_id, last_action.note_id, conn
)
sys.exit(0)
do_something = False
if args[0] == "-c":
if len(args) == 1:
print("missing note message")
sys.exit(1)
if args.create_name:
do_something = True
create_note(args, last_action.project_id, last_action.task_id, conn)
note = Note()
note.message = get_string_arg(args[1:], "note message")
create_note(note, last_action.project_id, last_action.task_id, conn)
elif args[0] == "-e":
if len(args) == 1:
print("nothing to update")
if args.delete_id:
do_something = True
delete_note(args.delete_id, conn)
note = Note()
args_i = 1
note_id = last_action.note_id
if args[args_i].isdigit():
note_id = arg_number(args[args_i])
args_i += 1
if args.edit_id:
do_something = True
edit_note(args, args.edit_id, conn)
if not note_id:
print("No active note to update")
sys.exit(1)
if not do_something:
list_note(last_action.project_id, last_action.task_id, last_action.note_id, conn)
note.message = get_string_arg(args[args_i:], "note message")
edit_note(note, note_id, last_action, conn)
elif args[0] == "-d":
# FIXME Duplicate code
if len(args) > 1:
delete_note(arg_number(args[1]), conn)
else:
delete_note(last_action.note_id, conn)
else:
print(f"Invalid note option: {args[0]}")
def create_note(args, active_project_id, active_task_id, conn):
# TODO Don't create note if no project (nothing selected?) ==> foreign key is not a solution: there is no project ID :)
logging.info('>> Create note')
def create_note(note, active_project_id, active_task_id, conn):
logging.info(">> Create note")
if active_task_id == 0:
print("No task selected")
sys.exit(1)
query = """
INSERT INTO note (project_id, task_id, username, message)
@@ -41,62 +73,100 @@ def create_note(args, active_project_id, active_task_id, conn):
"""
cursor = conn.cursor()
cursor.execute(query, (active_project_id, active_task_id, getpass.getuser(), args.create_name))
cursor.execute(
query, (active_project_id, active_task_id, getpass.getuser(), note.message)
)
note_id = cursor.lastrowid
action.create_action(cursor, project_id=active_project_id, task_id = active_task_id, note_id=note_id)
action_message = "{} (task {})".format(note.message, active_task_id)
record_action(
cursor,
TypeAction.CREATE,
action_message,
active_project_id,
active_task_id,
note_id,
)
logging.debug(action_message)
print('created note {}: {} (task {})'.format(note_id, args.create_name, active_task_id))
print("created note {}: {} (task {})".format(note_id, note.message, active_task_id))
def edit_note(args, note_id, conn):
logging.info('>> Edit note')
update_args = {}
if args.status:
update_args['status'] = args.status
if args.edit_name:
update_args['name'] = args.edit_name
def edit_note(note, note_id, last_action, conn):
logging.info(">> Edit note")
query = 'UPDATE note SET {} WHERE id = ?'
query = query.format(', '.join("%s = '%s'" % (k, v) for k, v in update_args.items()))
logging.debug('update note query: ' + query)
query = "UPDATE note SET message = ? WHERE id = ?"
cursor = conn.cursor()
if update_args:
logging.debug('Do a note update')
cursor.execute(query, (note_id,))
cursor.execute(query, (note.message, note_id))
log_args = ', '.join("%s: '%s'" % (k, v) for k, v in update_args.items())
print('updated note {}: ({})'.format(note_id, log_args))
else:
print('updated note {}: set active note')
if cursor.rowcount != 1:
logging.error("UPDATE FAILED")
print("could not find note {}".format(note_id))
sys.exit(1)
record_action(
cursor,
TypeAction.UPDATE,
note.message,
last_action.project_id,
last_action.task_id,
last_action.note_id,
)
print(
"updated note {}: (message: {}, task {})".format(
note_id, note.message, last_action.task_id
)
)
action.create_action(cursor, note_id, message = 'update ' + str(update_args))
def delete_note(note_id, conn):
logging.info('>> Remove note')
logging.info(">> Remove note")
query = """
DELETE FROM note
WHERE id = ?;
"""
note_name, task_id = get_note_name_task_id(note_id, conn)
query = "DELETE FROM note WHERE id = ?;"
cursor = conn.cursor()
cursor.execute(query, (note_id,))
if cursor.rowcount != 1:
logging.error('DELETE FAILED')
print('deleted note {}: {}'.format(note_id, 'note_name'))
logging.error("DELETE FAILED")
print("could not find note {}".format(note_id))
sys.exit(1)
log_message = "{} (task {})".format(note_name, task_id)
record_action(cursor, TypeAction.DELETE, log_message, note_id=note_id)
print("deleted note {}: {}".format(note_id, log_message))
def list_note(active_project_id, active_task_id, active_note_id, conn):
logging.info('>> No arguments')
query = "SELECT id, username, message FROM note WHERE project_id = ? AND task_id = ?;" #TODO Date & time
logging.info(">> No arguments")
query = """
SELECT id, username, message
FROM note
WHERE project_id = ?
AND task_id = ?;
"""
cursor = conn.cursor()
cursor.execute(query, (active_project_id, active_task_id))
for row in cursor.fetchall():
note_id, username, message = row
print('{:1} {:2d}: ({:8}) {}'.format('*' if active_note_id == note_id else '', note_id, username, message))
print(
"{:1} {:2d}: ({:8}) {}".format(
"*" if active_note_id == note_id else "", note_id, username, message
)
)
# 2: (budd) |open| |normal| test (0 notes)
# * 3: (budd) |open| |normal| Dec 31, 2018 tet (0 notes)
def get_note_name_task_id(note_id, conn):
query = "SELECT message, task_id FROM note WHERE id = ?"
cursor = conn.cursor()
cursor.execute(query, (note_id,))
return cursor.fetchone()

244
phelp.py Normal file
View File

@@ -0,0 +1,244 @@
import sys
from utils import strstr
def handle_help(args, *_):
if not args:
help_help()
sys.exit(0)
cmd_handler = [
("init", help_init),
("project", help_project),
("task", help_task),
("note", help_note),
("log", help_action),
("info", help_info),
("help", help_help),
("version", help_version),
]
candidate = -1
for i in range(len(cmd_handler)):
if strstr(cmd_handler[i][0], args[0]) == cmd_handler[i][0]:
if candidate < 0:
candidate = i
else:
print("Ambiguous command ({})".format(args[0]))
sys.exit(1)
if candidate < 0:
print("No help, <{}> is unknown command".format(args[0]))
help_help()
cmd_handler[candidate][1]()
def help_project():
print(
"""Pits project is basic entity used to group related tasks together. A project has name and status.
Creating a project:
$ pits project -c name [-s status]
Editing a project:
$ pits project -e [number] [-n name] [-s status]
Deleting a project:
$ pits project -d [number]
Viewing a project:
$ pits project [[-q] number]
Searching projects:
$ pits project -q [number | [-n name] [-s status]]
Examples:
$ pits project -c 'My Tasks'
created project 1: My Tasks (status: active)
$ pits project -c 'Feature Requests' -s backlog
created project 2: Feature Requests (status: backlog)
$ pits pro
1: (username) [active ] My Tasks (0 tasks)
* 2: (username) [backlog] Feature Requests (0 tasks)
$ pits p 1 -e -n 'Task and Bugs' -s current
1: Task and Bugs (current, 0 tasks)
$ pits p
1: (username) [current] Task and Bugs (0 tasks)
* 2: (username) [backlog] Feature Requests (0 tasks)
$ pits p -d
deleted project 2: Feature Requests
$ pits p
* 1: (username) [current] Task and Bugs (0 tasks)
"""
)
sys.exit(0)
def help_task():
print(
"""In Pits a task belongs to a project and projects can have many tasks. Task attributes include name, status,
priority, date, and time. All attributes except the name are optional.
Creating a task:
pits task -c name [-s status] [-p priority] [-d date] [-t time]
Editing a task:
pits task -e [number] [-n name] [-s status] [-p priority] [-d date] [-t time]
Moving a task:
pits task -m [number] -p number
Deleting a task:
pits task -d [number]
Viewing a task:
pits task [[-q] number]
Searching tasks:
pits task -q [number | [-n name] [-s status] [-p priority] [-d date-from] [-D date-to] [-t time-min] [-T time-max]]
Supported date formats:
none, 4/26, 4/26/2012, 4/26/12, '4/26 3pm', '4/26 19:30', '4/26/2012 3:15am', '4/26/12 17:00'
'Apr 26', 'Apr 26, 2012', 'Apr 26 3pm', 'Apr 26 19:30', 'Apr 26, 12 3:15am', 'Apr 26, 2012 17:00'
Supported time formats:
none, 17, 17:00, 17:30, 5pm, 1:15am
Examples:
$ pits task -c 'Hack this'
created task 1/1: Hack this (status: open, priority: normal)
$ pits task -c 'And hack that' -s new -p urgent -d 'Dec 31'
created task 2/1: And hack that (status: new, priority: urgent, date: Dec 31, 2010)
$ pits t
1: (username) [open] [normal] Hack this (0 notes)
* 2: (username) [new ] [urgent] Dec 31, 2010 And hack that (0 notes)
$ pits t -e 1 -s done -d 10/10 -t 4:30
updated task 1: Hack that (status: done, date: Oct 10, 2010, time: 4:30)
$ pits t
1: (username) [done] [normal] Oct 10, 2010 4:30 Hack this (0 notes)
* 2: (username) [new] [urgent] Dec 31, 2010 And hack that (0 notes)
$ pits t -d
deleted task 2: And hack that
$ pits t
1: (username) [done] [normal] Oct 10, 2010 4:30 Hack this (0 notes)
"""
)
sys.exit(0)
def help_note():
print(
"""Pits notes are attached to a task. The only attribute is the note's message body.
Creating a note:
$ pits note -c message
Editing a note:
$ pits note -e [number] message
Deleting a note:
$ pits note -d [number]
Listing notes:
$ pits note
"""
)
sys.exit(0)
def help_action():
print(
"""Show summary information about your Pits database. This command is as simple as:
pits log
"""
)
sys.exit(0)
def help_info():
print(
"""Show summary information about your Pits database. This command is as simple as:
pits info
"""
)
sys.exit(0)
def help_help():
print(
"""Pits is highly experimental software. Use it at your own risk. See LICENSE file for details.
usage: pits command [args]
The commands are:
init Create empty Pits database or reinitialize an existing one
project Create, search, and manage Pits projects
task Create, search, and manage Pits tasks
note Create, search, and manage Pits notes
log Show chronological Pits activity log
info Show summary information about your Pits database
help Show help information about Pit
version Show Pits version number
All commands might be shortened as long as they remain unambiguous. See 'pits help <command>' for more
information on a specific command.
"""
)
sys.exit(0)
def help_version():
print("todo")
sys.exit(0)
def help_init():
print(
"""Create empty Pits database or reinitialize an existing one. Default file name for the Pits database
is ~/.pits.db -- you can override the default by setting PITFILE environment variable.
$ pits init [-f]
-f force initialization without prompt
Example:
$ pits init
/home/user/.pits.db already exists, do you want to override it [y/N]: y
Created database /home/user/.pit
"""
)
sys.exit(0)

View File

@@ -1,87 +0,0 @@
import argparse
import logging
import pits #TODO Cyclic dependency
import project
import task
import note
import action
def help(args, last_action, conn):
logging.info('help function')
def create_parser():
parser = argparse.ArgumentParser()
parser.set_defaults(func=help)
subparsers = parser.add_subparsers()
# init Create empty Pit database or reinitialize an existing one
parser_init = subparsers.add_parser('init')#, usage='toto')
parser_init.add_argument('-f', '--force')
# parser_init.set_defaults(func=pits.init_database)
create_project_parser(subparsers)
create_task_parser(subparsers)
create_note_parser(subparsers)
# log Show chronological Pit activity log (== action)
parser_action = subparsers.add_parser('log')
parser_action.set_defaults(func=action.handle_action)
# info Show summary information about your Pit database
# Pit version: 0.1.0
# Pit file name: /home/budd/.pit
# Created by: budd on Jul 04, 2018 at 02:00
# Last updated by: budd on Jul 04, 2018 at 19:53
# Schema version: 1
# Projects: 1
# Tasks: 3
# Notes: 3
# Log entries: 9
# TODO
# help Show help information about Pit
# version Show Pit version number
parser.add_argument('--version', action='version', version='%(prog)s 1.0')
return parser
def create_project_parser(subparsers):
# PROJECT ARGS PARSER
# project Create, search, and manage Pit projects
parser_project = subparsers.add_parser('project')#, usage='toto')
group_project = parser_project.add_mutually_exclusive_group()
group_project.add_argument('-c', type=str, dest='create_name', metavar='name', help='name of project')
group_project.add_argument('-e', type=int, dest='edit_id', metavar='number', help='Edit a project')
group_project.add_argument('-d', type=int, dest='delete_id', nargs='?', const=-1, metavar='number', help='Delete a project')
parser_project.add_argument('-n', type=str, dest='edit_name', metavar='name', help='Edit: new name of project')
parser_project.add_argument('-s', type=str, dest='status', metavar='status')
parser_project.add_argument('-q', type=int)
parser_project.set_defaults(func=project.handle_project)
def create_task_parser(subparsers):
# TASK ARGS PARSER
# task Create, search, and manage Pit tasks
parser_task = subparsers.add_parser('task')
group_task = parser_task.add_mutually_exclusive_group()
group_task.add_argument('-c', type=str, dest='create_name', metavar='name', help='name of task')
group_task.add_argument('-e', type=int, dest='edit_id', metavar='number', help='Edit a task')
group_task.add_argument('-m', type=int, dest='moving_id', nargs='?', const=-1, metavar='number', help='Moving a task')
group_task.add_argument('-d', type=int, dest='delete_id', nargs='?', const=-1, metavar='number', help='Delete a task')
parser_task.add_argument('-n', type=str, dest='edit_name', metavar='name', help='Edit: new name of project')
parser_task.add_argument('-s', type=str, dest='status', metavar='status')
parser_task.add_argument('-q', type=int)
parser_task.add_argument('-i', type=int, dest='project_id', metavar='project id')
parser_task.set_defaults(func=task.handle_task)
def create_note_parser(subparsers):
# NOTE ARGS PARSER
# note Create, search, and manage Pit notes
parser_note = subparsers.add_parser('note')
group_note = parser_note.add_mutually_exclusive_group()
group_note.add_argument('-c', type=str, dest='create_name', metavar='name', help='name of task')
group_note.add_argument('-e', type=int, dest='edit_id', metavar='number', help='Edit a task')
group_note.add_argument('-d', type=int, dest='delete_id', nargs='?', const=-1, metavar='number', help='Delete a task')
parser_note.set_defaults(func=note.handle_note)

153
pits.py
View File

@@ -1,85 +1,138 @@
import sqlite3
import os
import argparse
import json
import getpass
import datetime
import logging
import sys
import project
import action
import task
import note
import pit_argparser
import phelp
import utils
# logging.basicConfig(level=logging.ERROR, format='%(levelname)7s :: %(message)s')
# logging.basicConfig(level=logging.DEBUG, format='%(asctime)s :: %(levelname)s :: %(message)s')
# logging.basicConfig(level=logging.DEBUG, format='%(levelname)7s :: %(message)s')
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s :: {%(filename)10s:%(lineno)d} :: %(funcName)s :: %(levelname)s :: %(message)s')
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s :: {%(filename)10s:%(lineno)3d} :: %(funcName)s :: %(levelname)s :: %(message)s",
)
DB_FILENAME = 'pits.db'
SCHEMA_FILENAME = 'pits_schema.sql'
DB_FILENAME = "pits.db"
SCHEMA_FILENAME = "pits_schema.sql"
def get_file_path(file_name):
return os.path.join(os.path.dirname(os.path.realpath(__file__)), file_name)
PIT_VERSION = "0.0.1"
SCHEMA_VERSION = 1
def info():
logging.info('info function')
def handle_note(args):
logging.info('>> handle note')
def pits_init():
db_path = utils.get_file_path(DB_FILENAME)
def create_connection(db_filename):
db_path = get_file_path(db_filename)
logging.debug("Try to connect to {}".format(db_path))
try:
conn = sqlite3.connect(db_path)
return conn
except Error as e:
print(e)
logging.debug("Search database in %s", db_path)
return None
def init():
# TODO Use init command like original pit
db_path = get_file_path(DB_FILENAME)
logging.debug('Search database in {}'.format(db_path))
db_is_new = not os.path.exists(db_path)
if not db_is_new:
logging.debug('Database already exist')
return
if os.path.exists(db_path):
valid = {"yes": True, "y": True, "ye": True}
print(
"{} already exists, do you want to override it? [y/N]:".format(db_path),
end="",
)
choice = input().lower()
if choice in valid:
os.remove(db_path)
else:
sys.exit(0)
with sqlite3.connect(db_path) as conn:
logging.info('Creating schema')
schema_path = get_file_path(SCHEMA_FILENAME)
logging.debug('Schema file path: {}'.format(schema_path))
logging.info("Creating schema")
schema_path = utils.get_file_path(SCHEMA_FILENAME)
logging.debug("Schema file path: %s", schema_path)
with open(schema_path, 'rt') as f:
with open(schema_path, "rt") as f:
schema = f.read()
conn.executescript(schema)
action_query = '''
action_query = """
INSERT INTO action (username, action, message)
VALUES (?, ?, ?);
'''
conn.execute(action_query, (getpass.getuser(), 'init', 'Initialized pit',)) # TODO Add schema version
"""
action_msg = "Initialized pits database - DB Schema version: {}".format(
SCHEMA_VERSION
)
conn.execute(action_query, (getpass.getuser(), "init", action_msg))
print("Created database " + db_path)
# logging.info('Inserting initial data')
# conn.executescript("""
# insert into project values(1, 'me', 'test', 'in progress', 'now', 'now')""")
if __name__ == '__main__':
init()
parser = pit_argparser.create_parser()
args = parser.parse_args()
conn = create_connection(DB_FILENAME)
last_action = action.read_last_action(conn)
logging.debug('Last action: {}'.format(last_action))
def pits_info(_1, _2, conn):
print("Pit version: {}".format(PIT_VERSION))
print("Pit file name: {}".format(utils.get_file_path(DB_FILENAME)))
print("Created by: {} on {}".format(*action.get_first_action(conn)))
print("Last updated by: {} on {}".format(*action.get_last_action(conn)))
print("Schema version: {}".format(SCHEMA_VERSION))
print("Projects: {}".format(utils.count_object(conn, "project")))
print("Tasks: {}".format(utils.count_object(conn, "task")))
print("Notes: {}".format(utils.count_object(conn, "note")))
print("Log entries: {}".format(utils.count_object(conn, "action")))
args.func(args, last_action, conn)
def pits_version(*_):
print("Pits version 0.1.0")
def main():
cmd_handler = [
("project", project.handle_project),
("task", task.handle_task),
("note", note.handle_note),
("log", action.handle_action),
("info", pits_info),
("help", phelp.handle_help),
("version", pits_version),
("init", pits_init),
]
candidate = -1
argv = sys.argv
if len(argv) == 1:
argv.append("help")
argv.append(None)
for i in range(len(cmd_handler)):
if utils.strstr(cmd_handler[i][0], argv[1]) == cmd_handler[i][0]:
if candidate < 0:
candidate = i
else:
print("Ambiguous command ({})".format(argv[1]))
sys.exit(1)
if candidate < 0:
print(
"Invalid command ({}), run '{} help' for help".format(
argv[1], utils.get_pits_path()
)
)
sys.exit(1)
if candidate == (len(cmd_handler) - 1):
pits_init()
sys.exit(0)
conn = utils.create_connection(DB_FILENAME)
last_action = action.read_current(conn)
logging.debug("Last action: %s", last_action)
logging.debug(argv)
logging.debug(argv[2:])
cmd_handler[candidate][1](argv[2:], last_action, conn)
conn.commit()
conn.close()
print('END')
print(os.path.dirname(os.path.abspath(__file__)))
if __name__ == "__main__":
main()

View File

@@ -1,18 +1,34 @@
-- schema.sql
-- Schema for pit in python / SQLite mode
-- v0.1
-- v1
--------------------------------
-- Project Table with trigger --
--------------------------------
CREATE TABLE project (
id integer primary key,
username text, -- User the project belongs to.
name text, -- Project name.
name text unique, -- Project name.
status text, -- Project status.
--number_of_tasks integer, -- Number of tasks for the project.
created_at date, -- When the project was created?
updatet_at date -- When the project was last updated?
updated_at date -- When the project was last updated?
);
CREATE TRIGGER project_insert_created_at AFTER INSERT ON project
BEGIN
UPDATE project SET created_at = strftime('%Y-%m-%d %H:%M:%S.%s', 'now', 'localtime') WHERE id = new.id;
END;
CREATE TRIGGER project_update_updated_at AFTER UPDATE ON project
BEGIN
UPDATE project SET updated_at = strftime('%Y-%m-%d %H:%M:%S.%s', 'now', 'localtime') WHERE id = new.id;
END;
--------------------------------
--------------------------------
-- Task Table with trigger --
--------------------------------
CREATE TABLE task (
id integer primary key, -- TODO When delete, counter go down ==> problem?? Use autoincrement?
project_id integer, -- Which project the task belongs to?
@@ -22,21 +38,24 @@ CREATE TABLE task (
priority text, -- Task priority.
date date, -- Generic date/time, ex: task deadline.
time date, -- Generic time, ex: time spent on the task.
-- number_of_notes integer, -- Number of notes for the task.
created_at date, -- When the task was created?
updated_at date -- When the task was last updated?
);
-- CREATE TRIGGER insert_task AFTER INSERT ON task
-- BEGIN
-- UPDATE project SET number_of_tasks = (SELECT count(*) FROM task WHERE project_id = new.project_id) WHERE id = new.project_id;
-- END;
CREATE TRIGGER task_insert_created_at AFTER INSERT ON task
BEGIN
UPDATE task SET created_at = strftime('%Y-%m-%d %H:%M:%S.%s', 'now', 'localtime') WHERE id = new.id;
END;
-- CREATE TRIGGER delete_task AFTER DELETE ON task
-- BEGIN
-- UPDATE project SET number_of_tasks = (SELECT count(*) FROM task WHERE project_id = old.project_id) WHERE id = old.project_id;
-- END;
CREATE TRIGGER task_update_updated_at AFTER UPDATE ON task
BEGIN
UPDATE task SET updated_at = strftime('%Y-%m-%d %H:%M:%S.%s', 'now', 'localtime') WHERE id = new.id;
END;
--------------------------------
--------------------------------
-- Note Table with trigger --
--------------------------------
CREATE TABLE note (
id integer primary key,
project_id integer, -- Project the note belongs to (0 if belongs to task).
@@ -47,6 +66,20 @@ CREATE TABLE note (
updated_at date -- When the note was last updated?
);
CREATE TRIGGER note_insert_created_at AFTER INSERT ON note
BEGIN
UPDATE note SET created_at = strftime('%Y-%m-%d %H:%M:%S.%s', 'now', 'localtime') WHERE id = new.id;
END;
CREATE TRIGGER note_update_updated_at AFTER UPDATE ON note
BEGIN
UPDATE note SET updated_at = strftime('%Y-%m-%d %H:%M:%S.%s', 'now', 'localtime') WHERE id = new.id;
END;
--------------------------------
--------------------------------
-- Action Table with trigger --
--------------------------------
CREATE TABLE action (
id integer primary key,
project_id integer, -- Project id (always set).
@@ -61,4 +94,66 @@ CREATE TABLE action (
CREATE TRIGGER action_insert_created_at AFTER INSERT ON action
BEGIN
UPDATE action SET created_at = strftime('%Y-%m-%d %H:%M:%S.%s', 'now', 'localtime') WHERE id = new.id;
END
END;
--------------------------------
--------------------------------
-- Current table with trigger --
--------------------------------
CREATE TABLE current (
project_id integer,
task_id integer,
note_id integer
);
INSERT INTO current VALUES (0,0,0);
-- Trigger after insert for current values
CREATE TRIGGER current_project AFTER INSERT ON project
BEGIN
UPDATE current SET project_id = new.id;
END;
CREATE TRIGGER current_task AFTER INSERT ON task
BEGIN
UPDATE current SET task_id = new.id;
END;
CREATE TRIGGER current_note AFTER INSERT ON note
BEGIN
UPDATE current SET note_id = new.id;
END;
-- Trigger after update for current values
CREATE TRIGGER update_project AFTER UPDATE ON project
BEGIN
UPDATE current SET project_id = new.id;
END;
CREATE TRIGGER update_task AFTER UPDATE ON task
BEGIN
UPDATE current SET task_id = new.id;
END;
CREATE TRIGGER update_note AFTER UPDATE ON note
BEGIN
UPDATE current SET note_id = new.id;
END;
-- Trigger after delete for current values
CREATE TRIGGER delete_project AFTER DELETE ON project
BEGIN
UPDATE current SET project_id = 0;
END;
CREATE TRIGGER delete_task AFTER DELETE ON task
BEGIN
UPDATE current SET task_id = 0;
END;
CREATE TRIGGER delete_note AFTER DELETE ON note
BEGIN
UPDATE current SET note_id = 0;
END;
--------------------------------

View File

@@ -1,98 +1,205 @@
# import sqlite3
import os
import argparse
import json
import getpass
import datetime
import logging
import sys
import sqlite3
import task
from action import record_action
from action import set_active
from action import TypeAction
from args import get_string_arg
from args import arg_number
from utils import get_pits_path
class Project:
def __init__(self, status=None):
self.name = None
self.status = status
def __str__(self):
return str(self.__dict__)
import action
def handle_project(args, last_action, conn):
logging.info('> handle project')
logging.debug('args: ' + str(args))
logging.info("> handle project")
logging.debug("args: %s", args)
do_something = False
if args.create_name:
do_something = True
create_project(args, conn)
if args.delete_id:
do_something = True
delete_project(args.delete_id, conn)
if args.edit_id:
do_something = True
edit_project(args, args.edit_id, conn)
if not do_something:
if not args:
list_project(last_action.project_id, conn)
sys.exit(0)
def create_project(args, conn):
# TODO Project is same name is forbidden
logging.info('>> Create project')
if args[0].isdigit():
view_project_set_active(int(args[0]), last_action, conn)
elif args[0] == "-c":
if len(args) == 1:
print("missing project name")
sys.exit(1)
project = Project("active")
project.name = get_string_arg(args[1:], "project name")
parse_project_args(project, args[2:])
create_project(project, conn)
elif args[0] == "-e":
if len(args) == 1:
print("nothing to update")
sys.exit(1)
project = Project()
if args[1].isdigit():
project_id = int(args[1])
parse_project_args(project, args[2:])
else:
project_id = last_action.project_id
parse_project_args(project, args[1:])
logging.debug("Project: (%s) %s", project_id, project)
edit_project(project, project_id, conn)
elif args[0] == "-d":
if len(args) > 1:
delete_project(arg_number(args[1]), conn)
else:
delete_project(last_action.project_id, conn)
else:
print(f"Invalid project option: {args[0]}")
def parse_project_args(project, args):
if not args:
return
i = 0
while i < len(args):
logging.debug(args[i])
if args[i] == "-s":
i += 1
project.status = get_string_arg(args[i : i + 1], "project status")
elif args[i] == "-n":
i += 1
project.name = get_string_arg(args[i : i + 1], "project name")
else:
print(f"Invalid project option: {args[i]}")
sys.exit(1)
i += 1
def create_project(project, conn):
logging.info(">> Create project")
query = """
INSERT INTO project (username, name, status, created_at)
VALUES (?, ?, ?, ?);
"""
status = 'active'
if args.status: status = args.status
cursor = conn.cursor()
try:
cursor.execute(
query,
(getpass.getuser(), project.name, project.status, datetime.datetime.now()),
)
project_id = cursor.lastrowid
action_message = "{} (status: {})".format(project.name, project.status)
record_action(cursor, TypeAction.CREATE, action_message, project_id=project_id)
logging.debug(action_message)
print(
"created project {}: {} (status: {})".format(
project_id, project.name, project.status
)
)
except sqlite3.IntegrityError as error:
print("project with the same name already exists")
logging.info(error)
def edit_project(project, project_id, conn):
# FIXME Duplicate code with edit_task
logging.info(">> Edit project")
update_args = [item for item in project.__dict__.items() if item[1] is not None]
if not update_args:
print("nothing to update")
print(
"Tips: if you want to active a project, just do '{} project <project_id>'".format(
get_pits_path()
)
)
sys.exit(1)
logging.debug("Project update args: %s", update_args)
# Retrieve name if not updated
project_name = None
if "name" not in update_args[0]:
project_name = get_project_name(project_id, conn)
query = "UPDATE project SET {} WHERE id = ?"
query = query.format(", ".join("%s = '%s'" % (k, v) for k, v in update_args))
logging.debug("update project query: %s", query)
cursor = conn.cursor()
cursor.execute(query, (getpass.getuser(), args.create_name, status, datetime.datetime.now(),))
logging.debug("Do a project update")
cursor.execute(query, (project_id,))
project_id = cursor.lastrowid
action_message = '{} (status: {})'.format(args.create_name, status)
action.create_action(cursor, project_id=project_id, message=action_message)
log_args = ", ".join("%s: %s" % (k, v) for k, v in update_args)
print('created project {}: {} (status: {})'.format(project_id, args.create_name, status))
def edit_project(args, project_id, conn):
logging.info('>> Edit project')
update_args = {}
if args.status:
update_args['status'] = args.status
if args.edit_name:
update_args['name'] = args.edit_name
query = 'UPDATE project SET {} WHERE id = ?'
query = query.format(', '.join("%s = '%s'" % (k, v) for k, v in update_args.items()))
logging.debug('update project query: ' + query)
cursor = conn.cursor()
if update_args:
logging.debug('Do a project update')
cursor.execute(query, (project_id,))
log_args = ', '.join("%s: '%s'" % (k, v) for k, v in update_args.items())
print('updated project {}: ({})'.format(project_id, log_args))
if project_name:
log_message = "{} ({})".format(project_name, log_args)
else:
print('updated project {}: set active project')
log_message = "({})".format(log_args)
print("updated project {}: {}".format(project_id, log_message))
record_action(cursor, TypeAction.UPDATE, log_message, project_id)
action.create_action(cursor, project_id, message = 'update ' + str(update_args))
def delete_project(project_id, conn):
logging.info('>> Remove project')
logging.info(">> Remove project")
query = """
DELETE FROM project
WHERE id = ?;
"""
project_name = get_project_name(project_id, conn)
cursor = conn.cursor()
# Cascade deleting: task
query = "SELECT id FROM task WHERE project_id = ?;"
cursor.execute(query, (project_id,))
deleted_task = 0
for row in cursor.fetchall():
task.delete_task(row[0], conn)
deleted_task += 1
query = "DELETE FROM project WHERE id = ?;"
cursor.execute(query, (project_id,))
if cursor.rowcount != 1:
logging.error('DELETE FAILED')
print('deleted project {}: {}'.format(project_id, 'project_name'))
logging.error("DELETE FAILED")
print("could not find project {}".format(project_id))
if deleted_task:
log_message = "{} with {} task{}".format(
project_name, deleted_task, "s" if deleted_task > 1 else ""
)
else:
log_message = project_name
print("deleted project {}: {}".format(project_id, log_message))
record_action(cursor, TypeAction.DELETE, log_message, project_id)
def list_project(active_project_id, conn):
logging.info('>> No arguments')
query = """
SELECT id, username, name, status, created_at,
SELECT id, username, name, status,
(SELECT count(*) FROM task WHERE task.project_id = project.id)
FROM project;
"""
@@ -100,7 +207,69 @@ def list_project(active_project_id, conn):
cursor = conn.cursor()
cursor.execute(query)
for row in cursor.fetchall():
logging.debug('Project row: {}'.format(row))
project_id, username, name, status, date, nb_task = row
print('{:1} {:2d}: ({:8}) | {} | {} ({} tasks)'.format('*' if active_project_id == project_id else '',
project_id, username, status, name, nb_task))
logging.debug("Project row: %s", row)
project_id, username, name, status, nb_task = row
# TODO Formatting track: https://stackoverflow.com/questions/9989334/create-nice-column-output-in-python
print(
"{:1} {:2d}: ({:8}) | {} | {} ({} tasks)".format(
"*" if active_project_id == project_id else "",
project_id,
username,
status,
name,
nb_task,
)
)
def view_project_set_active(project_id, last_action, conn):
# FIXME duplicate with list_project
query = """
SELECT id, username, name, status,
(SELECT count(*) FROM task WHERE task.project_id = project.id)
FROM project WHERE id = ?;
"""
cursor = conn.cursor()
cursor.execute(query, (project_id,))
row = cursor.fetchone()
if not row:
print("Could not find project {}".format(project_id))
sys.exit(1)
print("* {:d}: ({}) {} (status: {}, {} tasks)".format(*row))
# FIXME duplicate with list_task
query = """
SELECT id, username, name, status, priority, date, time,
(SELECT count(*) FROM note WHERE note.task_id = task.id)
FROM task WHERE project_id = ?
"""
cursor.execute(query, (project_id,))
for row in cursor.fetchall():
logging.debug("Task row: %s", row)
task_id, username, name, status, priority, date, time, nb_note = row
message = date + time + name
print(
" {} {:d}: ({}) [{}] [{}] {} ({} notes)".format(
"*" if last_action.task_id == row[0] else " ",
task_id,
username,
status,
priority,
message,
nb_note,
)
)
set_active(cursor, project_id=project_id)
# TODO Add a -v option to see notes?
def get_project_name(project_id, conn):
query = "SELECT name FROM project WHERE id = ?"
cursor = conn.cursor()
cursor.execute(query, (project_id,))
return cursor.fetchone()[0]

404
task.py
View File

@@ -1,43 +1,137 @@
# import sqlite3
import os
import argparse
import json
import getpass
import datetime
import logging
import sys
from action import record_action
from action import TypeAction
from action import set_active
from args import get_string_arg
from args import arg_number
from utils import get_pits_path
import note
class Task:
def __init__(self, status=None, priority=None):
self.name = None
self.status = status
self.priority = priority
self.date = None
self.time = None
import action
def handle_task(args, last_action, conn):
logging.info('> handle task')
logging.debug('args: ' + str(args))
logging.info("> handle task")
logging.debug("args: %s", args)
logging.debug('Last action: {}'.format(last_action))
do_something = False
if args.create_name:
do_something = True
create_task(args, last_action.project_id, conn)
if args.delete_id:
do_something = True
delete_task(args.delete_id, conn)
if args.moving_id:
do_something = True
moving_task(args)
if args.edit_id:
do_something = True
edit_task(args, args.edit_id, conn)
if not do_something:
if not args:
list_task(last_action.project_id, last_action.task_id, conn)
sys.exit(0)
def create_task(args, active_project_id, conn):
# TODO Don't create task if no project (nothing selected?) ==> foreign key is not a solution: there is no project ID :)
logging.info('>> Create task')
if args[0].isdigit():
view_task_set_active(arg_number(args[0]), last_action, conn)
elif args[0] == "-c":
if len(args) == 1:
print("missing task name")
sys.exit(1)
task = Task("open", "normal")
task.name = get_string_arg(args[1:], "task name")
parse_task_args(task, args[2:])
create_task(task, last_action.project_id, conn)
elif args[0] == "-e":
if len(args) == 1:
print("nothing to update")
sys.exit(1)
task = Task()
if args[1].isdigit():
task_id = int(args[1])
parse_task_args(task, args[2:])
else:
task_id = (
last_action.task_id
) # FIXME If no active tast - use incremental args_i
parse_task_args(task, args[1:])
logging.debug("Task: (%s) %s", task_id, task)
edit_task(task, task_id, conn)
elif args[0] == "-d":
# FIXME Duplicate code
if len(args) > 1:
delete_task(arg_number(args[1]), conn)
else:
delete_task(last_action.task_id, conn)
elif args[0] == "-m":
# Try another logic
args_i = 1
if len(args) < 3:
print("missing project number")
sys.exit(1)
task_id = last_action.task_id
# Task id
if args[args_i].isdigit():
task_id = arg_number(args[args_i])
args_i += 1
if not task_id:
print("No active task to move.")
sys.exit(1)
# Project option - if not fail
if args[args_i] != "-p":
print("missing project number")
sys.exit(1)
else:
args_i += 1
moving_task(task_id, arg_number(args[args_i]), conn)
else:
print(f"Invalid task option: {args[0]}")
def parse_task_args(task, args):
if not args:
return
i = 0
while i < len(args):
logging.debug(args[i])
if args[i] == "-s":
i += 1
task.status = get_string_arg(args[i : i + 1], "task status")
elif args[i] == "-n":
i += 1
task.name = get_string_arg(args[i : i + 1], "task name")
elif args[i] == "-p":
i += 1
task.priority = get_string_arg(args[i : i + 1], "task priority")
elif args[i] == "-d":
i += 1
task.date = get_string_arg(args[i : i + 1], "task date")
elif args[i] == "-t":
i += 1
task.time = get_string_arg(args[i : i + 1], "task time")
else:
print(f"Invalid task option: {args[i]}")
sys.exit(1)
i += 1
def create_task(task, project_id, conn):
logging.info(">> Create task")
if project_id == 0:
print("No project selected")
sys.exit(1)
query = """
INSERT INTO
@@ -45,84 +139,234 @@ def create_task(args, active_project_id, conn):
VALUES (?, ?, ?, ?, ?, ?, ?, ?);
"""
status = 'open'
priority = 'normal'
# TODO Date & time
date = ''
time = ''
date = ""
time = ""
cursor = conn.cursor()
cursor.execute(query, (active_project_id, getpass.getuser(), args.create_name, status, priority, date, time,
datetime.datetime.now(),))
cursor.execute(
query,
(
project_id,
getpass.getuser(),
task.name,
task.status,
task.priority,
date,
time,
datetime.datetime.now(),
),
)
task_id = cursor.lastrowid
logging.debug('CREATE ACTION ' + str(active_project_id) + str(task_id) )
logging.debug("CREATE ACTION " + str(project_id) + str(task_id))
action_message = '{} (status: {}, priority: {}, project: {})'.format(args.create_name, status, priority,
active_project_id)
# TODO if date/time/other => add to message
task_details = [
item
for item in task.__dict__.items()
if item[1] is not None and item[0] is not "name"
]
log_args = ", ".join("%s: %s" % (k, v) for k, v in task_details)
action.create_action(cursor, project_id=active_project_id, task_id=task_id, message=action_message)
print('created task {}: {} (status: {})'.format(task_id, args.create_name, status))
action_message = "{} ({}, project: {})".format(task.name, log_args, project_id)
def edit_task(args, task_id, conn):
logging.info('>> Edit task')
record_action(cursor, TypeAction.CREATE, action_message, project_id, task_id)
print("created task {}: {}".format(task_id, action_message))
update_args = {}
if args.status:
update_args['status'] = args.status
if args.edit_name:
update_args['name'] = args.edit_name
query = 'UPDATE task SET {} WHERE id = ?'
query = query.format(', '.join("%s = '%s'" % (k, v) for k, v in update_args.items()))
logging.debug('update task query: ' + query)
def edit_task(task, task_id, conn):
# FIXME Duplicate code with edit_project
logging.info(">> Edit task")
update_args = [item for item in task.__dict__.items() if item[1] is not None]
if not update_args:
print("nothing to update")
print(
"Tips: if you want to activate a task, just do '{} task <task_id>'".format(
get_pits_path()
)
)
sys.exit(1)
logging.debug("Task update args: %s", update_args)
# Retrieve name if not updated
task_name = None
if "name" not in update_args[0]:
task_name, _ = get_task_name_project_id(task_id, conn)
query = "UPDATE task SET {} WHERE id = ?"
query = query.format(", ".join("%s = '%s'" % (k, v) for k, v in update_args))
logging.debug("update task query: %s")
cursor = conn.cursor()
if update_args:
logging.debug('Do a task update')
cursor.execute(query, (task_id,))
logging.debug("Do a task update")
cursor.execute(query, (task_id,))
log_args = ', '.join("%s: '%s'" % (k, v) for k, v in update_args.items())
print('updated task {}: ({})'.format(task_id, log_args))
log_args = ", ".join("%s: %s" % (k, v) for k, v in update_args)
if task_name:
log_message = "{} ({})".format(task_name, log_args)
else:
print('updated task {}: set active task')
log_message = "({})".format(log_args)
# TODO If not edit name... => retrieve name ?
# TODO Retrieve project id ?
action_message = '{} ({})'.format(args.edit_name, ', '.join("%s: %s" % (k, v) for k, v in update_args.items()))
logging.debug('UPDATE TASK - ACTION MESSAGE : ' + action_message)
action.create_action(cursor, task_id=task_id, action=action.TypeAction.UPDATE, message = action_message)
print("updated task {}: {}".format(task_id, log_message))
logging.debug("UPDATE TASK - ACTION MESSAGE : %s", log_args)
record_action(cursor, TypeAction.UPDATE, log_message, task_id=task_id)
def moving_task(task_id, dest_project_id, conn):
logging.info(">> Moving task")
_, or_project_id = get_task_name_project_id(task_id, conn)
if or_project_id == dest_project_id:
print("Task already in project {} - nothing to do".format(dest_project_id))
sys.exit(0)
query = "UPDATE task SET project_id = ? WHERE id = ?;"
cursor = conn.cursor()
cursor.execute(query, (dest_project_id, task_id))
if cursor.rowcount != 1:
print("Problem occur when moving task...")
sys.exit(1)
print(
"moved task {}: from project {} to project {}".format(
task_id, or_project_id, dest_project_id
)
)
record_action(
cursor,
TypeAction.MOVE,
"from project {} to project {}".format(or_project_id, dest_project_id),
project_id=dest_project_id,
task_id=task_id,
)
def moving_task(args):
print('moving')
def delete_task(task_id, conn):
logging.info('>> Remove task')
logging.info(">> Remove task")
query = """
DELETE FROM task
WHERE id = ?;
"""
task_name, project_id = get_task_name_project_id(task_id, conn)
cursor = conn.cursor()
# Cascade deleting: note
query = "SELECT id FROM note WHERE task_id = ?;"
cursor.execute(query, (task_id,))
deleted_note = 0
for row in cursor.fetchall():
note_id = row[0]
note.delete_note(note_id, conn)
deleted_note += 1
# FIXME Use a delete_note_for_task function in note module?
# Remove task
query = "DELETE FROM task WHERE id = ?;"
cursor.execute(query, (task_id,))
if cursor.rowcount != 1:
logging.error('DELETE FAILED')
print('deleted task {}: {}'.format(task_id, 'task_name'))
logging.error("DELETE FAILED")
print("could not find task {}".format(task_id))
sys.exit(1)
if deleted_note:
log_message = "{} with {} note{} (project: {})".format(
task_name, deleted_note, "s" if deleted_note > 1 else "", project_id
)
else:
log_message = "{} (project: {})".format(task_name, project_id)
print("deleted task {}: {}".format(task_id, log_message))
record_action(cursor, TypeAction.DELETE, log_message, project_id, task_id)
def list_task(active_project_id, active_task_id, conn):
logging.info('>> No arguments')
query = "SELECT id, username, status, priority, name FROM task WHERE project_id = ?;" #TODO Date & time
logging.info(">> No arguments")
query = """
SELECT id, username, status, priority, name,
(SELECT count(*) FROM note WHERE note.task_id = task.id)
FROM task WHERE project_id = ?;
""" # TODO Date & time
cursor = conn.cursor()
cursor.execute(query, (active_project_id,))
for row in cursor.fetchall():
nb_note = 0
task_id, username, status, priority, name = row
print('{:1} {:2d}: ({:8}) | {} | {} | {} ({} tasks )'.format('*' if active_task_id == task_id else '', task_id,
username, status, priority, name, nb_note))
task_id, username, status, priority, name, nb_note = row
print(
"{:1} {:2d}: ({:8}) | {} | {} | {} ({} notes)".format(
"*" if active_task_id == task_id else "",
task_id,
username,
status,
priority,
name,
nb_note,
)
)
# 2: (budd) |open| |normal| test (0 notes)
# * 3: (budd) |open| |normal| Dec 31, 2018 tet (0 notes)
def view_task_set_active(task_id, last_action, conn):
# FIXME duplicate with list_task / view_project_set_active
# Retrieve selected task
query = """
SELECT id, username, name, project_id, status, priority,
(SELECT count(*) FROM note WHERE note.task_id = task.id)
FROM task WHERE id = ?;
""" # TODO Date & time
cursor = conn.cursor()
cursor.execute(query, (task_id,))
row = cursor.fetchone()
if not row:
print("Could not find task {}".format(task_id))
sys.exit(1)
print(
"* {:d}: ({}) {} (project: {}, status: {}, priority: {}, {} note(s))".format(
*row
)
)
# FIXME duplicate with list_note
query = """
SELECT id, username, message FROM note WHERE task_id = ?
"""
cursor.execute(query, (task_id,))
for row in cursor.fetchall():
logging.debug("Note row: %s", row)
note_id, username, message = row
print(
" {} {:d}: ({}) {}".format(
"*" if last_action.note_id == row[0] else " ",
note_id,
username,
message,
)
)
set_active(cursor, task_id=task_id)
def get_task_name_project_id(task_id, conn):
""" Return a tuple with task name and project id of task"""
query = "SELECT name, project_id FROM task WHERE id = ?"
cursor = conn.cursor()
cursor.execute(query, (task_id,))
return cursor.fetchone()
# $ pit t 11
# * 11: (budd) a task (project: 1, status: in progress, priority: high, date: Apr 26, 2018, time: 5:00, 2 notes)
# 6: (budd) bonjour le monde
# * 7: (budd) bonjour le monde

55
utils.py Normal file
View File

@@ -0,0 +1,55 @@
import os
import sys
import sqlite3
import logging
def get_file_path(file_name):
return os.path.join(os.path.dirname(os.path.realpath(__file__)), file_name)
def get_pits_path():
return os.path.basename(sys.argv[0])
def strstr(haystack, needle):
"""
Return a sub-string of haystack, going from first occurence of needle until the end.
Return None if not found.
Arguments:
haystack {string} -- the entry string
needle {string} -- the part of string searched
Returns:
string -- sub-string of haystack, starting from needle. None if not exist.
Examples:
email = 'name@example.com'
domain = strstr(email, '@');
print(domain) // Display : @example.com
"""
pos = haystack.find(needle)
if pos < 0: # not found
return None
return haystack[pos:]
def create_connection(db_filename):
db_path = get_file_path(db_filename)
logging.debug("Try to connect to %s", db_path)
try:
conn = sqlite3.connect(db_path)
return conn
except sqlite3.Error as sqlite3_error:
print(sqlite3_error)
return None
def count_object(conn, table_name):
query = "SELECT count(*) FROM " + table_name
cursor = conn.cursor()
cursor.execute(query)
return cursor.fetchone()[0]