mirror of
https://git.webmeisterei.com/webmeisterei/todoist-taskwarrior.git
synced 2023-12-21 10:23:00 +01:00
236 lines
5.6 KiB
Python
236 lines
5.6 KiB
Python
import click
|
|
import os
|
|
import sys
|
|
|
|
from taskw import TaskWarrior
|
|
from todoist.api import TodoistAPI
|
|
from . import utils
|
|
|
|
todoist = None
|
|
taskwarrior = None
|
|
|
|
""" CLI Commands """
|
|
|
|
@click.group()
|
|
def cli():
|
|
pass
|
|
|
|
|
|
@cli.command()
|
|
def synchronize():
|
|
"""Sync the local task database and then exit. """
|
|
|
|
important('Syncing tasks with todoist... ', nl=False)
|
|
todoist.sync()
|
|
success('OK')
|
|
|
|
|
|
@cli.command()
|
|
@click.option('-i', '--interactive', is_flag=True, default=False)
|
|
@click.option('--sync/--no-sync', default=True)
|
|
@click.pass_context
|
|
def migrate(ctx, interactive, sync):
|
|
if sync:
|
|
ctx.invoke(synchronize)
|
|
|
|
tasks = todoist.items.all()
|
|
important(f'Starting migration of {len(tasks)}...\n')
|
|
for idx, task in enumerate(tasks):
|
|
data = {}
|
|
tid = data['tid'] = task['id']
|
|
name = data['name'] = task['content']
|
|
data['project'] = todoist.projects.get_by_id(task['project_id'])['name']
|
|
data['priority'] = utils.parse_priority(task['priority'])
|
|
data['tags'] = [
|
|
todoist.labels.get_by_id(l_id)['name']
|
|
for l_id in task['labels']
|
|
]
|
|
data['entry'] = utils.parse_date(task['date_added'])
|
|
data['due'] = utils.parse_date(task['due_date_utc'])
|
|
data['recur'] = utils.parse_recur(task['date_string'])
|
|
|
|
important(f'Task {idx + 1} of {len(tasks)}: {name}\n')
|
|
|
|
if check_task_exists(tid):
|
|
info(f'Already exists (todoist_id={tid})\n')
|
|
elif not interactive:
|
|
add_task(**data)
|
|
else:
|
|
task_prompt(**data)
|
|
|
|
|
|
def check_task_exists(tid):
|
|
""" Given a Taskwarrior ID, check if the task exists """
|
|
taskwarrior_id, _ = taskwarrior.get_task(todoist_id=tid)
|
|
return taskwarrior_id is not None
|
|
|
|
|
|
def add_task(tid, name, project, tags, priority, entry, due, recur):
|
|
"""Add a taskwarrior task from todoist task
|
|
|
|
Returns the taskwarrior task.
|
|
"""
|
|
info(f"Importing '{name}' ({project}) - ", nl=False)
|
|
try:
|
|
tw_task = taskwarrior.task_add(
|
|
name,
|
|
project=project,
|
|
tags=tags,
|
|
priority=priority,
|
|
entry=entry,
|
|
due=due,
|
|
recur=recur,
|
|
todoist_id=tid,
|
|
)
|
|
except:
|
|
error('FAILED')
|
|
else:
|
|
success('OK')
|
|
return tw_task
|
|
|
|
|
|
def task_prompt(**task_data):
|
|
"""Interactively add tasks
|
|
|
|
y - add task
|
|
n - skip task
|
|
r - rename task
|
|
p - change priority
|
|
t - change tags
|
|
q - quit immediately
|
|
? - print help
|
|
"""
|
|
callbacks = {
|
|
'y': lambda: task_data,
|
|
'n': lambda: task_data,
|
|
|
|
# Rename
|
|
'r': lambda: {
|
|
**task_data,
|
|
'name': name_prompt(task_data['name']),
|
|
},
|
|
|
|
# Edit tags
|
|
't': lambda: {
|
|
**task_data,
|
|
'tags': tags_prompt(task_data['tags']),
|
|
},
|
|
|
|
# Edit priority
|
|
'p': lambda: {
|
|
**task_data,
|
|
'priority': priority_prompt(task_data['priority']),
|
|
},
|
|
|
|
# Quit
|
|
'q': lambda: exit(1),
|
|
|
|
# Help message
|
|
'?': lambda: task_prompt_help() or task_data,
|
|
}
|
|
|
|
response = None
|
|
while response not in ('y', 'n'):
|
|
prompt_text = (
|
|
stringify_task(**task_data)
|
|
+ important_msg(f"\nImport this task?")
|
|
)
|
|
response = click.prompt(
|
|
prompt_text,
|
|
type=click.Choice(callbacks.keys()),
|
|
show_choices=True,
|
|
)
|
|
|
|
# Execute operation
|
|
task_data = callbacks[response]()
|
|
|
|
if response == 'n':
|
|
error('Skipping task\n')
|
|
return
|
|
|
|
return add_task(**task_data)
|
|
|
|
|
|
def task_prompt_help():
|
|
lines = [
|
|
x.strip() for x in
|
|
task_prompt.__doc__.split('\n')
|
|
]
|
|
error('\n'.join(lines))
|
|
|
|
|
|
def tags_prompt(tags):
|
|
return click.prompt(
|
|
important_msg('Set tags'),
|
|
default=' '.join(tags),
|
|
show_default=False,
|
|
value_proc=lambda x: x.split(' ')
|
|
)
|
|
|
|
|
|
def priority_prompt(priority):
|
|
return click.prompt(
|
|
important_msg('Set priority'),
|
|
default='',
|
|
type=click.Choice([None, 'L', 'M', 'H']),
|
|
value_proc=lambda x: None if '' else x,
|
|
show_default=False,
|
|
)
|
|
|
|
|
|
def name_prompt(name):
|
|
return click.prompt(
|
|
important_msg('Set name'),
|
|
default=name,
|
|
value_proc=lambda x: x.strip()
|
|
)
|
|
|
|
|
|
""" Output Utils """
|
|
|
|
def important(msg, **kwargs):
|
|
click.echo(important_msg(msg), **kwargs)
|
|
|
|
def important_msg(msg):
|
|
return click.style(msg, fg='blue', bold=True)
|
|
|
|
def info(msg, **kwargs):
|
|
click.echo(msg, **kwargs)
|
|
|
|
def success(msg, **kwargs):
|
|
click.echo(click.style(msg, fg='green', bold=True))
|
|
|
|
def error(msg, **kwargs):
|
|
click.echo(click.style(msg, fg='red', bold=True))
|
|
|
|
def stringify_task(**task_data):
|
|
string = ''
|
|
for key, value in task_data.items():
|
|
key = click.style(key, underline=True)
|
|
if isinstance(value, list):
|
|
value = ' '.join(value)
|
|
elif value is None:
|
|
value = ''
|
|
string += f'{key}: {value}\n'
|
|
return string
|
|
|
|
|
|
""" Entrypoint """
|
|
|
|
if __name__ == '__main__':
|
|
is_help_cmd = '-h' in sys.argv or '--help' in sys.argv
|
|
todoist_api_key = os.getenv('TODOIST_API_KEY')
|
|
if todoist_api_key is None and not is_help_cmd:
|
|
exit('TODOIST_API_KEY environment variable not specified. Exiting.')
|
|
|
|
todoist = TodoistAPI(todoist_api_key)
|
|
|
|
# Create the TaskWarrior client, overriding config to
|
|
# create a `todoist_id` field which we'll use to
|
|
# prevent duplicates
|
|
taskwarrior = TaskWarrior(config_overrides={
|
|
'uda.todoist_id.type': 'string',
|
|
})
|
|
cli()
|
|
|