mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-10 15:17:57 -05:00
819 lines
29 KiB
Python
Executable File
819 lines
29 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import asyncio, os, sys, tempfile
|
|
from datetime import datetime
|
|
import time
|
|
from tabulate import tabulate
|
|
from colorama import Fore, Style
|
|
|
|
import api, lib.util
|
|
|
|
known_attrs = ["desc", "rank", "due", "project"]
|
|
|
|
async def add_task(task_args, server_name, port):
|
|
task = {
|
|
"title": None,
|
|
"tags": [],
|
|
"desc": None,
|
|
"assign": [],
|
|
"project": [],
|
|
"due": None,
|
|
"rank": None,
|
|
"created_at": lib.util.now(),
|
|
"state": "open"
|
|
}
|
|
# Everything that isn't an attribute is part of the title
|
|
# Open text editor if desc isn't set to write desc text
|
|
title_words = []
|
|
for arg in task_args:
|
|
if arg[0] == "+":
|
|
tag = arg
|
|
if tag in task["tags"]:
|
|
print(f"error: duplicate tag {tag} in task", file=sys.stderr)
|
|
sys.exit(-1)
|
|
task["tags"].append(tag)
|
|
elif arg[0] == "@":
|
|
assign = arg
|
|
if assign in task["assign"]:
|
|
print(f"error: duplicate assign {assign} in task", file=sys.stderr)
|
|
sys.exit(-1)
|
|
task["assign"].append(assign)
|
|
elif ":" in arg and arg.split(":")[0] in known_attrs:
|
|
attr, val = arg.split(":", 1)
|
|
set_task_attr(task, attr, val)
|
|
else:
|
|
title_words.append(arg)
|
|
|
|
title = " ".join(title_words)
|
|
if len(title) == 0:
|
|
print("Error: Title is required")
|
|
exit(-1)
|
|
task["title"] = title
|
|
if task["desc"] is None:
|
|
task["desc"] = prompt_description_text(task)
|
|
|
|
if task["desc"].strip() == '':
|
|
print("Abort adding the task due to empty description.")
|
|
exit(-1)
|
|
|
|
if task["rank"] is not None:
|
|
task["rank"] = round(task["rank"], 4)
|
|
|
|
try:
|
|
if task["ref_id"].strip() == '':
|
|
task.pop('ref_id')
|
|
if task["workspace"].strip() == '':
|
|
task.pop('workspace')
|
|
except KeyError:
|
|
pass
|
|
|
|
ref = await api.add_task(task, server_name, port)
|
|
if ref:
|
|
return ref, title
|
|
else:
|
|
print("You don't have write access")
|
|
exit(-1)
|
|
|
|
def prompt_text(comment_lines):
|
|
temp = tempfile.NamedTemporaryFile()
|
|
temp.write(b"\n")
|
|
for line in comment_lines:
|
|
temp.write(line.encode() + b"\n")
|
|
temp.flush()
|
|
editor = os.environ.get('EDITOR') if os.environ.get('EDITOR') else 'nano'
|
|
os.system(f"{editor} {temp.name}")
|
|
desc = open(temp.name, "r").read()
|
|
# Remove comments and empty lines from desc
|
|
cleaned = []
|
|
for line in desc.split("\n"):
|
|
if line == "# ------------------------ >8 ------------------------":
|
|
break
|
|
if line.startswith("#"):
|
|
continue
|
|
cleaned.append(line)
|
|
|
|
return "\n".join(cleaned)
|
|
|
|
def prompt_description_text(task):
|
|
return prompt_text([
|
|
"# Write task description above this line.",
|
|
"# These lines will be removed.",
|
|
"# An empty description aborts adding the task",
|
|
"\n# ------------------------ >8 ------------------------",
|
|
"# Do not modify or remove the line above.",
|
|
"# Everything below it will be ignored.",
|
|
f"\n{tabulate_task(task, True)}"
|
|
])
|
|
|
|
def prompt_comment_text():
|
|
return prompt_text([
|
|
"# Write comments above this line",
|
|
"# These lines will be removed"
|
|
])
|
|
|
|
def prompt_description_edit(text):
|
|
return prompt_text([
|
|
f"{text}"
|
|
"# Edit the task description above this line",
|
|
"# These lines will be removed"
|
|
])
|
|
|
|
def set_task_attr(task, attr, val):
|
|
if attr not in known_attrs:
|
|
print(f"Error: invalid attribute: {attr} {val}")
|
|
print("Task is not added")
|
|
exit(-1)
|
|
|
|
if val.lower() == "none":
|
|
task[attr] = None
|
|
else:
|
|
val = convert_attr_val(attr, val)
|
|
task[attr] = val
|
|
|
|
lib.util._enforce_task_format(task)
|
|
|
|
def convert_attr_val(attr, val):
|
|
templ = lib.util.task_template
|
|
|
|
if attr in ["desc", "title"]:
|
|
assert templ[attr] == str
|
|
return val
|
|
elif attr == "rank":
|
|
try:
|
|
return float(val)
|
|
except ValueError:
|
|
print(f"error: rank value {val} isn't convertable to float",
|
|
file=sys.stderr)
|
|
sys.exit(-1)
|
|
elif attr == "due":
|
|
# Other date formats not yet supported... ez to add
|
|
if len(val) != 4:
|
|
print(f"Error: due date must be of length 4 in mmyy format")
|
|
sys.exit(-1)
|
|
date = datetime.now().date()
|
|
year = int(date.strftime("%Y"))%100
|
|
try:
|
|
dt = datetime.strptime(f"18:00 {val}{year}", "%H:%M %d%m%y")
|
|
if dt.date() < date:
|
|
dt = datetime.strptime(f"18:00 {val}{year+1}", "%H:%M %d%m%y")
|
|
except ValueError:
|
|
print(f"error: unknown date format {val}")
|
|
sys.exit(-1)
|
|
due = lib.util.datetime_to_unix(dt)
|
|
return due
|
|
elif attr == "project":
|
|
try:
|
|
return [val]
|
|
except ValueError:
|
|
print(f"error: project value {val} isn't convertable to list",
|
|
file=sys.stderr)
|
|
sys.exit(-1)
|
|
else:
|
|
print(f"error: unhandled attr '{attr}' = {val}")
|
|
sys.exit(-1)
|
|
|
|
async def show_active_tasks(workspace, server_name, port):
|
|
refids = await api.get_ref_ids(server_name, port)
|
|
tasks = []
|
|
for refid in refids:
|
|
tasks.append(await api.fetch_task(refid, server_name, port))
|
|
list_tasks(tasks, workspace, [])
|
|
|
|
async def show_deactive_tasks(month_ts, workspace, server_name, port):
|
|
tasks = await api.fetch_deactive_tasks(month_ts, server_name, port)
|
|
list_tasks(tasks, workspace, [])
|
|
|
|
async def show_log(server_name, port, timeframe):
|
|
# fetch all tasks
|
|
refids = await api.get_ref_ids(server_name, port)
|
|
tasks = await api.fetch_deactive_tasks(None, server_name, port)
|
|
for refid in refids:
|
|
tasks.append(await api.fetch_task(refid, server_name, port))
|
|
|
|
# list tasks events within a timeframe
|
|
if timeframe == "day":
|
|
timestamp = 86400
|
|
elif timeframe == "week" or timeframe == None:
|
|
timestamp = 604800
|
|
elif timeframe == "month":
|
|
timestamp = 2628002
|
|
else:
|
|
print(f"Error: invalid timeframe {timeframe}")
|
|
print("valid timeframes are: 'day', 'week' and 'month'")
|
|
print("try: tau log week")
|
|
exit(-1)
|
|
res_events = []
|
|
now = lib.util.now()
|
|
for task in tasks:
|
|
events = task["events"]
|
|
for event in events:
|
|
# if timestamp is in ms convert it to s
|
|
event_ts = int(event["timestamp"])
|
|
if event_ts > 10e10:
|
|
event_ts //= 1000
|
|
date = lib.util.unix_to_datetime(event_ts)
|
|
if (now - event_ts) < timestamp:
|
|
if event["action"] == "state":
|
|
action = "stopped" if event["content"] == "stop" else f"{event["content"]}ed"
|
|
res = f"{date}: {event["author"]} {action} task: ({task["ref_id"][:6]}) '{task["title"]}'"
|
|
res_events.append(res)
|
|
if event["action"] == "comment":
|
|
res = f"{date}: {event["author"]} commented on task: ({task["ref_id"][:6]}) '{task["title"]}'"
|
|
res_events.append(res)
|
|
if event["action"] in ["assign", "tags"] :
|
|
res = f"{date}: {event["author"]} added {event["content"][1:]} to {event["action"]} in task: ({task["ref_id"][:6]}) '{task["title"]}'"
|
|
res_events.append(res)
|
|
for i in res_events:
|
|
print(i)
|
|
|
|
def list_tasks(tasks, workspace, filters):
|
|
print(f"Workspace: {workspace}")
|
|
headers = ["ID", "Title", "Status", "Project",
|
|
"Tags", "assign", "Rank", "Due", "RefID"]
|
|
table_rows = []
|
|
for id, task in enumerate(tasks, 1):
|
|
if task is None:
|
|
continue
|
|
if is_filtered(task, filters):
|
|
continue
|
|
ref_id = task["ref_id"][:6]
|
|
title = task["title"]
|
|
status = task["state"]
|
|
# project = task["project"] if task["project"] is not None else ""
|
|
tags = " ".join(f"+{tag}" for tag in task["tags"])
|
|
assign = " ".join(f"@{assign}" for assign in task["assign"])
|
|
project = " ".join(f"{project}" for project in task["project"])
|
|
if task["due"] is None:
|
|
due = ""
|
|
else:
|
|
dt = lib.util.unix_to_datetime(task["due"])
|
|
due = dt.strftime("%H:%M %d/%m/%y")
|
|
|
|
rank = round(task["rank"], 4) if task["rank"] is not None else ""
|
|
|
|
if status == "start":
|
|
id = Fore.GREEN + str(id) + Style.RESET_ALL
|
|
title = Fore.GREEN + str(title) + Style.RESET_ALL
|
|
status = Fore.GREEN + str(status) + Style.RESET_ALL
|
|
project = Fore.GREEN + str(project) + Style.RESET_ALL
|
|
tags = Fore.GREEN + str(tags) + Style.RESET_ALL
|
|
assign = Fore.GREEN + str(assign) + Style.RESET_ALL
|
|
rank = Fore.GREEN + str(rank) + Style.RESET_ALL
|
|
due = Fore.GREEN + str(due) + Style.RESET_ALL
|
|
ref_id = Fore.GREEN + str(ref_id) + Style.RESET_ALL
|
|
elif status == "pause":
|
|
id = Fore.YELLOW + str(id) + Style.RESET_ALL
|
|
title = Fore.YELLOW + str(title) + Style.RESET_ALL
|
|
status = Fore.YELLOW + str(status) + Style.RESET_ALL
|
|
project = Fore.YELLOW + str(project) + Style.RESET_ALL
|
|
tags = Fore.YELLOW + str(tags) + Style.RESET_ALL
|
|
assign = Fore.YELLOW + str(assign) + Style.RESET_ALL
|
|
rank = Fore.YELLOW + str(rank) + Style.RESET_ALL
|
|
due = Fore.YELLOW + str(due) + Style.RESET_ALL
|
|
ref_id = Fore.YELLOW + str(ref_id) + Style.RESET_ALL
|
|
elif status == "stop":
|
|
id = Fore.RED + str(id) + Style.RESET_ALL
|
|
title = Fore.RED + str(title) + Style.RESET_ALL
|
|
status = Fore.RED + str(status) + Style.RESET_ALL
|
|
project = Fore.RED + str(project) + Style.RESET_ALL
|
|
tags = Fore.RED + str(tags) + Style.RESET_ALL
|
|
assign = Fore.RED + str(assign) + Style.RESET_ALL
|
|
rank = Fore.RED + str(rank) + Style.RESET_ALL
|
|
due = Fore.RED + str(due) + Style.RESET_ALL
|
|
ref_id = Fore.RED + str(ref_id) + Style.RESET_ALL
|
|
else:
|
|
#id = Style.DIM + str(id) + Style.RESET_ALL
|
|
#title = Style.DIM + str(title) + Style.RESET_ALL
|
|
#status = Style.DIM + str(status) + Style.RESET_ALL
|
|
project = Style.DIM + str(project) + Style.RESET_ALL
|
|
tags = Style.DIM + str(tags) + Style.RESET_ALL
|
|
#assign = Style.DIM + str(assign) + Style.RESET_ALL
|
|
rank = Style.DIM + str(rank) + Style.RESET_ALL
|
|
due = Style.DIM + str(due) + Style.RESET_ALL
|
|
#ref_id = Style.DIM + str(ref_id) + Style.RESET_ALL
|
|
|
|
rank_value = task["rank"] if task["rank"] is not None else 0
|
|
row = [
|
|
id,
|
|
title,
|
|
status,
|
|
project,
|
|
tags,
|
|
assign,
|
|
rank,
|
|
due,
|
|
ref_id
|
|
]
|
|
table_rows.append((rank_value, row))
|
|
|
|
table = [row for (_, row) in
|
|
sorted(table_rows, key=lambda item: item[0], reverse=True)]
|
|
print(tabulate(table, headers=headers))
|
|
|
|
async def show_task(refid, server_name, port):
|
|
task = await api.fetch_task(refid, server_name, port)
|
|
task_table(task)
|
|
return 0
|
|
|
|
async def show_archive_task(ref_id, month_ts, server_name, port):
|
|
task = await api.fetch_archive_task(ref_id, month_ts, server_name, port)
|
|
task_table(task)
|
|
return 0
|
|
|
|
def tabulate_task(task, prompt):
|
|
tags = " ".join(f"+{tag}" for tag in task["tags"])
|
|
assign = " ".join(f"{assign}" for assign in task["assign"])
|
|
project = " ".join(f"{project}" for project in task["project"])
|
|
rank = round(task["rank"], 4) if task["rank"] is not None else ""
|
|
if task["due"] is None:
|
|
due = ""
|
|
else:
|
|
dt = lib.util.unix_to_datetime(task["due"])
|
|
due = dt.strftime("%H:%M %d/%m/%y")
|
|
|
|
assert task["created_at"] is not None
|
|
dt = lib.util.unix_to_datetime(task["created_at"])
|
|
created_at = dt.strftime("%H:%M %d/%m/%y")
|
|
|
|
if prompt:
|
|
task["ref_id"] = ''
|
|
task["workspace"] = ''
|
|
|
|
table = [
|
|
["RefID:", task["ref_id"]],
|
|
["Title:", task["title"]],
|
|
["Workspace:", task["workspace"]],
|
|
["Description:", task["desc"]],
|
|
["Status:", task["state"]],
|
|
["Project:", project],
|
|
["Tags:", tags],
|
|
["Assign:", assign],
|
|
["Rank:", rank],
|
|
["Due:", due],
|
|
["Created:", created_at],
|
|
]
|
|
return tabulate(table, headers=["Attribute", "Value"])
|
|
|
|
def task_table(task):
|
|
print(tabulate_task(task, False))
|
|
|
|
table = []
|
|
for event in task["events"]:
|
|
act, who, when, args = event["action"], event["author"], event["timestamp"], event["content"]
|
|
when = lib.util.unix_to_datetime(when)
|
|
when = when.strftime("%H:%M %d/%m/%y")
|
|
|
|
if act == "due" and when is not None:
|
|
due_date = lib.util.unix_to_datetime(args)
|
|
due_date = due_date.strftime("%H:%M %d/%m/%y")
|
|
table.append([
|
|
Style.DIM + f"{who} changed {act} to {due_date}" + Style.RESET_ALL,
|
|
"",
|
|
Style.DIM + when + Style.RESET_ALL
|
|
])
|
|
elif act == "tags" or act == "assign":
|
|
val = f"{args}"
|
|
event = f"{who} added {val} to {act}"
|
|
if val[0] == "-":
|
|
event = f"{who} removed {val[1:]} from {act}"
|
|
table.append([
|
|
Style.DIM + event + Style.RESET_ALL,
|
|
"",
|
|
Style.DIM + when + Style.RESET_ALL
|
|
])
|
|
elif act == "state":
|
|
status = args
|
|
if status == "pause":
|
|
status_verb = "paused"
|
|
elif status in ["start", "open"]:
|
|
status_verb = f"{status}ed"
|
|
elif status == "stop":
|
|
status_verb = f"stopped"
|
|
else:
|
|
print(f"internal error: unhandled task state {status}",
|
|
file=sys.stderr)
|
|
sys.exit(-2)
|
|
|
|
table.append([
|
|
f"{who} {status_verb} task",
|
|
"",
|
|
Style.DIM + when + Style.RESET_ALL
|
|
])
|
|
elif act == "comment":
|
|
continue
|
|
else:
|
|
table.append([
|
|
Style.DIM + f"{who} changed {act} to {args}" + Style.RESET_ALL,
|
|
"",
|
|
Style.DIM + when + Style.RESET_ALL
|
|
])
|
|
print(tabulate(table))
|
|
|
|
table = []
|
|
for event in task['events']:
|
|
act, who, when, args = event["action"], event["author"], event["timestamp"], event["content"]
|
|
when = lib.util.unix_to_datetime(when)
|
|
when = when.strftime("%H:%M %d/%m/%y")
|
|
if act == "comment":
|
|
comment = args
|
|
table.append([
|
|
f"{who}>",
|
|
wrap_comment(comment, 58),
|
|
Style.DIM + when + Style.RESET_ALL
|
|
])
|
|
if len(table) > 0:
|
|
print("Comments:")
|
|
print(tabulate(table))
|
|
|
|
def wrap_comment(comment, width):
|
|
lines = []
|
|
line_start = 0
|
|
for i, char in enumerate(comment):
|
|
if char == ' ' and (i - line_start >= width):
|
|
lines.append(comment[line_start:i + 1])
|
|
line_start = i + 1
|
|
|
|
if line_start < len(comment):
|
|
lines.append(comment[line_start:])
|
|
return '\n'.join(lines)
|
|
|
|
async def modify_task(refid, args, server_name, port):
|
|
task = await api.fetch_task(refid, server_name, port)
|
|
current_assigns = task["assign"]
|
|
current_tags = task["tags"]
|
|
changes = {}
|
|
changes["assign"] = []
|
|
changes["tags"] = []
|
|
for arg in args:
|
|
# This must go before the next elif block
|
|
if arg.startswith("@") or (arg.startswith("-@") and arg[2:] in current_assigns):
|
|
changes["assign"].append(arg)
|
|
elif arg.startswith("+") or (arg.startswith("-") and arg[1:] in current_tags):
|
|
changes["tags"].append(arg)
|
|
elif arg.lower() in ["desc", "description"]:
|
|
desc = task["desc"]
|
|
new_desc = prompt_description_edit(desc).lstrip()
|
|
if desc == new_desc:
|
|
print("Abort due to unchanged description")
|
|
exit(-1)
|
|
changes["desc"] = new_desc
|
|
elif ":" in arg:
|
|
attr, val = arg.split(":", 1)
|
|
if val.lower() == "none":
|
|
if attr not in ["project", "rank", "due"]:
|
|
print(f"error: invalid you cannot set {attr} to none",
|
|
file=sys.stderr)
|
|
return -1
|
|
val = None
|
|
else:
|
|
val = convert_attr_val(attr, val)
|
|
changes[str(attr)] = val
|
|
else:
|
|
print(f"warning: unknown arg '{arg}'. Skipping...", file=sys.stderr)
|
|
if not await api.modify_task(refid, changes, server_name, port):
|
|
print("You don't have write access")
|
|
exit(-1)
|
|
return 0
|
|
|
|
async def change_task_status(refid, status, server_name, port):
|
|
task = await api.fetch_task(refid, server_name, port)
|
|
assert task is not None
|
|
title = task["title"]
|
|
|
|
if not await api.change_task_status(refid, status, server_name, port):
|
|
return -1
|
|
|
|
if status == "start":
|
|
print(f"Started task '{title}'")
|
|
elif status == "pause":
|
|
print(f"Paused task '{title}'")
|
|
elif status == "stop":
|
|
print(f"Completed task '{title}'")
|
|
elif status == "open":
|
|
print(f"Opened task '{title}'")
|
|
|
|
return 0
|
|
|
|
async def comment(refid, args, server_name, port):
|
|
if not args:
|
|
comment = prompt_comment_text()
|
|
else:
|
|
comment = " ".join(args)
|
|
|
|
if comment.strip() == '':
|
|
print("Abort adding comment due to empty content.")
|
|
exit(-1)
|
|
|
|
if not await api.add_task_comment(refid, comment, server_name, port):
|
|
print("You don't have write access")
|
|
exit(-1)
|
|
|
|
# Two json rpcs back to back cause Unexpected EOF error
|
|
time.sleep(0.1)
|
|
task = await api.fetch_task(refid, server_name, port)
|
|
assert task is not None
|
|
title = task["title"]
|
|
print(f"Commented on task '{title}'")
|
|
return 0
|
|
|
|
def is_filtered(task, filters):
|
|
for fltr in filters:
|
|
if fltr.startswith("+"):
|
|
tag = fltr[1:]
|
|
if tag not in task["tags"]:
|
|
return True
|
|
elif fltr.startswith("@"):
|
|
assign = fltr[1:]
|
|
if assign not in task["assign"]:
|
|
return True
|
|
elif ":" in fltr:
|
|
attr, val = fltr.split(":", 1)
|
|
if val.lower() == "none":
|
|
if attr not in ["project", "rank", "due"]:
|
|
print(f"error: invalid you cannot set {attr} to none",
|
|
file=sys.stderr)
|
|
sys.exit(-1)
|
|
if task[attr] is not None:
|
|
return True
|
|
elif attr == "state" :
|
|
if val not in ["open", "start", "pause"]:
|
|
print(f"error: invalid, filter by {attr} can only be [\"open\", \"start\", \"pause\"]",
|
|
file=sys.stderr)
|
|
sys.exit(-1)
|
|
if task["state"] != val:
|
|
return True
|
|
else:
|
|
val = convert_attr_val(attr, val)
|
|
if task[attr] != val:
|
|
return True
|
|
else:
|
|
print(f"error: unknown arg '{fltr}'", file=sys.stderr)
|
|
sys.exit(-1)
|
|
|
|
return False
|
|
|
|
def find_free_id(task_ids):
|
|
for i in range(1, 1000):
|
|
if i not in task_ids:
|
|
return i
|
|
1
|
|
|
|
def map_ids(task_ids, ref_ids):
|
|
return dict(zip(task_ids, ref_ids))
|
|
|
|
async def main():
|
|
val = str('127.0.0.1:23330')
|
|
allowed_states = ["start", "pause", "stop", "open"]
|
|
|
|
for i in range(1, len(sys.argv)):
|
|
if sys.argv[i] == "-e":
|
|
val = sys.argv[i+1]
|
|
del sys.argv[i]
|
|
del sys.argv[i]
|
|
break
|
|
|
|
server_name, port = val.split(':')
|
|
|
|
refids = await api.get_ref_ids(server_name, port)
|
|
free_ids = []
|
|
tasks = []
|
|
for refid in refids:
|
|
tasks.append(await api.fetch_task(refid, server_name, port))
|
|
free_ids.append(find_free_id(free_ids))
|
|
|
|
data = map_ids(free_ids, refids)
|
|
|
|
workspace = await api.get_workspace(server_name, port)
|
|
|
|
if len(sys.argv) == 1:
|
|
await show_active_tasks(workspace, server_name, port)
|
|
return 0
|
|
|
|
if any(x in ["-h", "--help", "help"] for x in sys.argv):
|
|
print('''USAGE:
|
|
tau [OPTIONS] [SUBCOMMAND]
|
|
|
|
OPTIONS:
|
|
-h, --help Print help information
|
|
-e RPC endpoint [default: 127.0.0.1:23330]
|
|
|
|
SUBCOMMANDS:
|
|
add Add a new task.
|
|
archive Show completed tasks.
|
|
comment Write comment for task by id.
|
|
modify Modify an existing task by id.
|
|
pause Pause task(s).
|
|
start Start task(s).
|
|
stop Stop task(s).
|
|
switch Switch between configured workspaces.
|
|
show List filtered tasks.
|
|
export Save current workspace tasks to a path.
|
|
import Load current workspace tasks from a path.
|
|
help Show this help text.
|
|
|
|
Examples:
|
|
tau add task one due:0312 rank:1.022 project:zk +lol @sk desc:desc +abc +def
|
|
tau add task two rank:1.044 project:cr +mol @up desc:desc2
|
|
tau add task three due:0512 project:zy +trol @kk desc:desc3 +who
|
|
tau 1 modify @upgr due:1112 rank:none
|
|
tau 1 modify -@up
|
|
tau 1 modify -mol -xx
|
|
tau 1,2 modify +dev @erto
|
|
tau 1-3 start
|
|
tau 1 comment "this is an awesome comment"
|
|
tau 2 pause
|
|
tau show @erto state:start # list started tasks that are assigned to 'erto'
|
|
tau show +dev project:zk # list tasks with 'dev' tag project 'zk'
|
|
tau switch darkfi # switch to configured 'darkfi' workspace
|
|
tau archive # current month's completed tasks
|
|
tau archive 1122 # completed tasks of Nov. 2022
|
|
tau archive 1122 1 # show info of task completed in Nov. 2022
|
|
''')
|
|
return 0
|
|
elif sys.argv[1] == "log":
|
|
if len(sys.argv) == 3:
|
|
timeframe = sys.argv[2]
|
|
else:
|
|
timeframe = None
|
|
await show_log(server_name, port, timeframe)
|
|
return 0
|
|
elif sys.argv[1] == "add":
|
|
task_args = sys.argv[2:]
|
|
ref, title = await add_task(task_args, server_name, port)
|
|
if title:
|
|
print(f"Created task ({find_free_id(free_ids)}) ({ref[:7]}) '{title}'.")
|
|
return 0
|
|
elif sys.argv[1] == "archive":
|
|
if len(sys.argv) == 4:
|
|
if len(sys.argv[2]) == 4:
|
|
month = sys.argv[2]
|
|
month_ts = lib.util.month_to_unix(month)
|
|
else:
|
|
print("error: usage format is: tau archive [MONTH] [ID]")
|
|
return -1
|
|
|
|
archive_refids = await api.get_archive_ref_ids(month_ts, server_name, port)
|
|
afree_ids = []
|
|
atasks = []
|
|
for arefid in archive_refids:
|
|
atasks.append(await api.fetch_archive_task(arefid, month_ts, server_name, port))
|
|
afree_ids.append(find_free_id(afree_ids))
|
|
|
|
adata = map_ids(afree_ids, archive_refids)
|
|
|
|
if len(sys.argv[3]) < 4:
|
|
try:
|
|
tid = int(sys.argv[3])
|
|
arefid = adata[tid]
|
|
except (ValueError, KeyError):
|
|
print("error: invalid ID", file=sys.stderr)
|
|
return -1
|
|
else:
|
|
print("error: invalid ID", file=sys.stderr)
|
|
return -1
|
|
|
|
|
|
if (errc := await show_archive_task(arefid, month_ts, server_name, port)) < 0:
|
|
return errc
|
|
elif len(sys.argv) == 3:
|
|
if sys.argv[2] == "all":
|
|
await show_deactive_tasks(None, workspace, server_name, port)
|
|
elif len(sys.argv[2]) == 4:
|
|
month = sys.argv[2]
|
|
month_ts = lib.util.month_to_unix(month)
|
|
await show_deactive_tasks(month_ts, workspace, server_name, port)
|
|
elif len(sys.argv[2]) < 4:
|
|
month_ts = lib.util.month_to_unix()
|
|
archive_refids = await api.get_archive_ref_ids(month_ts, server_name, port)
|
|
afree_ids = []
|
|
atasks = []
|
|
for arefid in archive_refids:
|
|
atasks.append(await api.fetch_archive_task(arefid, month_ts, server_name, port))
|
|
afree_ids.append(find_free_id(afree_ids))
|
|
|
|
adata = map_ids(afree_ids, archive_refids)
|
|
|
|
try:
|
|
tid = int(sys.argv[2])
|
|
arefid = adata[tid]
|
|
except (ValueError, KeyError):
|
|
print("error: invalid ID", file=sys.stderr)
|
|
return -1
|
|
|
|
if (errc := await show_archive_task(arefid, month_ts, server_name, port)) < 0:
|
|
return errc
|
|
else:
|
|
print("error: month must be of format MMYY")
|
|
return -1
|
|
else:
|
|
month_ts = lib.util.month_to_unix()
|
|
await show_deactive_tasks(month_ts, workspace, server_name, port)
|
|
|
|
return 0
|
|
elif sys.argv[1] == "show":
|
|
if len(sys.argv) > 2:
|
|
filters = sys.argv[2:]
|
|
list_tasks(tasks, workspace, filters)
|
|
else:
|
|
await show_active_tasks(workspace, server_name, port)
|
|
return 0
|
|
elif sys.argv[1] == "switch":
|
|
if not len(sys.argv) == 3:
|
|
print("Error: you must provide workspace name")
|
|
return 0
|
|
if not await api.switch_workspace(sys.argv[2], server_name, port):
|
|
print(f"Error: Workspace \"{sys.argv[2]}\" is not configured.")
|
|
else:
|
|
print(f"You are now on \"{sys.argv[2]}\" workspace.")
|
|
return 0
|
|
elif sys.argv[1] == "export":
|
|
if len(sys.argv) == 2:
|
|
path = "~/.local/share/darkfi"
|
|
else:
|
|
path = sys.argv[2]
|
|
if await api.export_to(path, server_name, port):
|
|
print(f"Exported tasks successfuly to {path}")
|
|
return 0
|
|
elif sys.argv[1] == "import":
|
|
if len(sys.argv) == 2:
|
|
path = "~/.local/share/darkfi"
|
|
else:
|
|
path = sys.argv[2]
|
|
if await api.import_from(path, server_name, port):
|
|
print(f"Imported tasks successfuly from {path}")
|
|
return 0
|
|
|
|
try:
|
|
id = sys.argv[1]
|
|
subcommands = ["modify", "comment"]
|
|
if any(id in ls for ls in [allowed_states, subcommands]):
|
|
user_input = input("This command has no filter, and will modify all tasks. Are you sure? [y/N] ")
|
|
if user_input.lower() in ['y', 'yes']:
|
|
refid = list(refids)
|
|
args = sys.argv[1:]
|
|
else:
|
|
print("Command prevented from running.")
|
|
exit(-1)
|
|
elif any(id == rfid[:len(id)] for rfid in refids if len(id) > 2):
|
|
refid = []
|
|
for rid in refids:
|
|
if id == rid[:len(id)]:
|
|
refid.append(rid)
|
|
args = sys.argv[2:]
|
|
else:
|
|
lines = id.split(',')
|
|
numbers = []
|
|
for line in lines:
|
|
if line == '':
|
|
continue
|
|
elif '-' in line:
|
|
t = line.split('-')
|
|
numbers += range(int(t[0]), int(t[1]) + 1)
|
|
else:
|
|
numbers.append(int(line))
|
|
refid = []
|
|
for i in numbers:
|
|
refid.append(data[i])
|
|
args = sys.argv[2:]
|
|
except (ValueError, KeyError):
|
|
print("error: invalid ID", file=sys.stderr)
|
|
return -1
|
|
except EOFError:
|
|
print('\nOperation is cancelled')
|
|
return -1
|
|
|
|
if not args:
|
|
for rid in refid:
|
|
await show_task(rid, server_name, port)
|
|
return 0
|
|
|
|
subcmd, args = args[0], args[1:]
|
|
|
|
if subcmd == "modify":
|
|
if not args:
|
|
print("Error: modify subcommand must have at least one argument.")
|
|
exit(-1)
|
|
for rid in refid:
|
|
if (errc := await modify_task(rid, args, server_name, port)) < 0:
|
|
return errc
|
|
time.sleep(0.1)
|
|
await show_task(rid, server_name, port)
|
|
elif subcmd in allowed_states:
|
|
status = subcmd
|
|
for rid in refid:
|
|
if (errc := await change_task_status(rid, status, server_name, port)) < 0:
|
|
return errc
|
|
time.sleep(0.1)
|
|
elif subcmd == "comment":
|
|
for rid in refid:
|
|
if (errc := await comment(rid, args, server_name, port)) < 0:
|
|
return errc
|
|
else:
|
|
print(f"error: unknown subcommand '{subcmd}'")
|
|
return -1
|
|
|
|
return 0
|
|
|
|
asyncio.run(main())
|
|
|