General clean-up of code

pull/15/head
Hoffelhas 2021-01-16 17:14:22 +01:00
parent 17cc3a8418
commit 376e176d6b
1 changed files with 772 additions and 676 deletions

View File

@ -8,10 +8,10 @@ import argparse
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
import time import time
global overview_item_ids
global overview_item_labels
# Makes --help text wider # Makes --help text wider
def make_wide(formatter, w=120, h=36): def make_wide(formatter, w=120, h=36):
"""Return a wider HelpFormatter, if possible.""" """Return a wider HelpFormatter, if possible."""
try: try:
@ -24,62 +24,10 @@ def make_wide(formatter, w=120, h=36):
logging.error("Argparse help formatter failed, falling back.") logging.error("Argparse help formatter failed, falling back.")
return formatter return formatter
def main(): # Sync with Todoist API
# Version
current_version = 'v1.4.1'
"""Main process function.""" def sync(api):
parser = argparse.ArgumentParser(
formatter_class=make_wide(argparse.HelpFormatter, w=110, h=50))
parser.add_argument('-a', '--api_key',
help='Takes your Todoist API Key.', type=str)
parser.add_argument(
'-l', '--label', help='Enable next action labelling. Define which label to use.', type=str)
parser.add_argument(
'-r', '--recurring', help='Enable regeneration of sub-tasks in recurring lists.', action='store_true')
parser.add_argument(
'-e', '--end', help='Enable alternative end-of-day time instead of default midnight. Enter a number from 1 to 24 to define which hour is used.', type=int)
parser.add_argument(
'-d', '--delay', help='Specify the delay in seconds between syncs (default 5).', default=5, type=int)
parser.add_argument(
'-pp', '--pp_suffix', help='Change suffix for parallel-parallel labeling (default "//").', default='//')
parser.add_argument(
'-ss', '--ss_suffix', help='Change suffix for sequential-sequential labeling (default "--").', default='--')
parser.add_argument(
'-ps', '--ps_suffix', help='Change suffix for parallel-sequential labeling (default "/-").', default='/-')
parser.add_argument(
'-sp', '--sp_suffix', help='Change suffix for sequential-parallel labeling (default "-/").', default='-/')
parser.add_argument(
'-df', '--dateformat', help='Strptime() format of starting date (default "%%d-%%m-%%Y").', default = '%d-%m-%Y')
parser.add_argument(
'-hf', '--hide_future', help='Prevent labelling of future tasks beyond a specified number of days.', default = 0, type=int)
parser.add_argument(
'--onetime', help='Update Todoist once and exit.', action='store_true')
parser.add_argument(
'--nocache', help='Disables caching data to disk for quicker syncing.', action='store_true')
parser.add_argument('--debug', help='Enable detailed debugging in log.',
action='store_true')
parser.add_argument('--inbox', help='The method the Inbox should be processed with.',
default=None, choices=['parallel', 'sequential'])
args = parser.parse_args()
# Set debug
if args.debug:
log_level = logging.DEBUG
else:
log_level = logging.INFO
logging.basicConfig(level=log_level,
format='%(asctime)s %(levelname)-8s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
handlers=[logging.FileHandler(
'debug.log', 'w+', 'utf-8'),
logging.StreamHandler()]
)
# Sync with Todoist API
def sync(api):
try: try:
logging.debug('Syncing the current state from the API') logging.debug('Syncing the current state from the API')
api.sync() api.sync()
@ -88,8 +36,10 @@ def main():
'Error trying to sync with Todoist API: %s' % str(e)) 'Error trying to sync with Todoist API: %s' % str(e))
quit() quit()
# Simple query for yes/no answer # Simple query for yes/no answer
def query_yes_no(question, default="yes"):
def query_yes_no(question, default="yes"):
# """Ask a yes/no question via raw_input() and return their answer. # """Ask a yes/no question via raw_input() and return their answer.
# "question" is a string that is presented to the user. # "question" is a string that is presented to the user.
@ -121,8 +71,10 @@ def main():
sys.stdout.write("Please respond with 'yes' or 'no' " sys.stdout.write("Please respond with 'yes' or 'no' "
"(or 'y' or 'n').\n") "(or 'y' or 'n').\n")
# Initialisation of Autodoist # Initialisation of Autodoist
def initialise(args):
def initialise(args):
# Check we have a API key # Check we have a API key
if not args.api_key: if not args.api_key:
@ -152,7 +104,8 @@ def main():
logging.info("You are running with the following functionalities:\n\n Next action labelling mode: {}\n Regenerate sub-tasks mode: {}\n Shifted end-of-day mode: {}\n".format(*modes)) logging.info("You are running with the following functionalities:\n\n Next action labelling mode: {}\n Regenerate sub-tasks mode: {}\n Shifted end-of-day mode: {}\n".format(*modes))
if m_num == 0: if m_num == 0:
logging.info("\n No functionality has been enabled. Please see --help for the available options.\n") logging.info(
"\n No functionality has been enabled. Please see --help for the available options.\n")
exit(0) exit(0)
# Run the initial sync # Run the initial sync
@ -179,7 +132,8 @@ def main():
logging.info( logging.info(
"\n\nLabel '{}' doesn't exist in your Todoist\n".format(args.label)) "\n\nLabel '{}' doesn't exist in your Todoist\n".format(args.label))
# sys.exit(1) # sys.exit(1)
response = query_yes_no('Do you want to automatically create this label?') response = query_yes_no(
'Do you want to automatically create this label?')
if response: if response:
api.labels.add(args.label) api.labels.add(args.label)
@ -200,8 +154,10 @@ def main():
return api, label_id return api, label_id
# Check for Autodoist update # Check for Autodoist update
def check_for_update(current_version):
def check_for_update(current_version):
updateurl = 'https://api.github.com/repos/Hoffelhas/autodoist/releases' updateurl = 'https://api.github.com/repos/Hoffelhas/autodoist/releases'
try: try:
@ -227,9 +183,12 @@ def main():
logging.error("Error while checking for updates: {}".format(e)) logging.error("Error while checking for updates: {}".format(e))
return 1 return 1
# Assign current type based on settings # Assign current type based on settings
def check_name(name):
len_suffix = [len(args.pp_suffix), len(args.ss_suffix), len(args.ps_suffix), len(args.sp_suffix)]
def check_name(args, name):
len_suffix = [len(args.pp_suffix), len(args.ss_suffix),
len(args.ps_suffix), len(args.sp_suffix)]
if name == 'Inbox': if name == 'Inbox':
current_type = args.inbox current_type = args.inbox
@ -241,19 +200,24 @@ def main():
current_type = 'p-s' current_type = 'p-s'
elif name[-len_suffix[1]:] == args.sp_suffix: elif name[-len_suffix[1]:] == args.sp_suffix:
current_type = 's-p' current_type = 's-p'
elif args.ps_suffix == '/-' and name[-2:] == '_-': # Workaround for section names, which don't allow / symbol. # Workaround for section names, which don't allow / symbol.
elif args.ps_suffix == '/-' and name[-2:] == '_-':
current_type = 'p-s' current_type = 'p-s'
elif args.sp_suffix == '-/' and name[-2:] == '-_': # Workaround for section names, which don't allow / symbol. # Workaround for section names, which don't allow / symbol.
elif args.sp_suffix == '-/' and name[-2:] == '-_':
current_type = 's-p' current_type = 's-p'
elif args.pp_suffix == '//' and name[-1:] == '_': # Workaround for section names, which don't allow / symbol. # Workaround for section names, which don't allow / symbol.
elif args.pp_suffix == '//' and name[-1:] == '_':
current_type = 'parallel' current_type = 'parallel'
else: else:
current_type = None current_type = None
return current_type return current_type
# Scan the end of a name to find what type it is # Scan the end of a name to find what type it is
def get_type(object, key):
def get_type(args, object, key):
object_name = '' object_name = ''
@ -271,7 +235,7 @@ def main():
except: except:
pass pass
current_type = check_name(object_name) current_type = check_name(args, object_name)
# Check if project type changed with respect to previous run # Check if project type changed with respect to previous run
if old_type == current_type: if old_type == current_type:
@ -282,28 +246,34 @@ def main():
return current_type, type_changed return current_type, type_changed
# Determine a project type # Determine a project type
def get_project_type(project_object):
def get_project_type(args, project_object):
"""Identifies how a project should be handled.""" """Identifies how a project should be handled."""
project_type, project_type_changed = get_type( project_type, project_type_changed = get_type(
project_object, 'project_type') args, project_object, 'project_type')
return project_type, project_type_changed return project_type, project_type_changed
# Determine a section type # Determine a section type
def get_section_type(section_object):
def get_section_type(args, section_object):
"""Identifies how a section should be handled.""" """Identifies how a section should be handled."""
if section_object is not None: if section_object is not None:
section_type, section_type_changed = get_type( section_type, section_type_changed = get_type(
section_object, 'section_type') args, section_object, 'section_type')
else: else:
section_type = None section_type = None
section_type_changed = 0 section_type_changed = 0
return section_type, section_type_changed return section_type, section_type_changed
# Determine an item type # Determine an item type
def get_item_type(item, project_type):
def get_item_type(args, item, project_type):
"""Identifies how an item with sub items should be handled.""" """Identifies how an item with sub items should be handled."""
if project_type is None and item['parent_id'] != 0: if project_type is None and item['parent_id'] != 0:
@ -312,14 +282,16 @@ def main():
item_type_changed = 1 item_type_changed = 1
item['item_type'] = item_type item['item_type'] = item_type
except: except:
item_type, item_type_changed = get_type(item, 'item_type') item_type, item_type_changed = get_type(args, item, 'item_type')
else: else:
item_type, item_type_changed = get_type(item, 'item_type') item_type, item_type_changed = get_type(args, item, 'item_type')
return item_type, item_type_changed return item_type, item_type_changed
# Logic to add a label to an item # Logic to add a label to an item
def add_label(item, label):
def add_label(item, label, overview_item_ids, overview_item_labels):
if label not in item['labels']: if label not in item['labels']:
labels = item['labels'] labels = item['labels']
logging.debug('Updating \'%s\' with label', item['content']) logging.debug('Updating \'%s\' with label', item['content'])
@ -331,8 +303,10 @@ def main():
overview_item_ids[str(item['id'])] = 1 overview_item_ids[str(item['id'])] = 1
overview_item_labels[str(item['id'])] = labels overview_item_labels[str(item['id'])] = labels
# Logic to remove a label from an item # Logic to remove a label from an item
def remove_label(item, label):
def remove_label(item, label, overview_item_ids, overview_item_labels):
if label in item['labels']: if label in item['labels']:
labels = item['labels'] labels = item['labels']
logging.debug('Removing \'%s\' of its label', item['content']) logging.debug('Removing \'%s\' of its label', item['content'])
@ -344,16 +318,20 @@ def main():
overview_item_ids[str(item['id'])] = -1 overview_item_ids[str(item['id'])] = -1
overview_item_labels[str(item['id'])] = labels overview_item_labels[str(item['id'])] = labels
# Ensure labels are only issued once per item # Ensure labels are only issued once per item
def update_labels(label_id):
def update_labels(api, label_id, overview_item_ids, overview_item_labels):
filtered_overview_ids = [ filtered_overview_ids = [
k for k, v in overview_item_ids.items() if v != 0] k for k, v in overview_item_ids.items() if v != 0]
for item_id in filtered_overview_ids: for item_id in filtered_overview_ids:
labels = overview_item_labels[item_id] labels = overview_item_labels[item_id]
api.items.update(item_id, labels=labels) api.items.update(item_id, labels=labels)
# To handle items which have no sections # To handle items which have no sections
def create_none_section():
def create_none_section():
none_sec = { none_sec = {
'id': None, 'id': None,
'name': 'None', 'name': 'None',
@ -361,7 +339,10 @@ def main():
} }
return none_sec return none_sec
def check_header(level): # Check if header logic needs to be applied
def check_header(level):
header_all_in_level = False header_all_in_level = False
unheader_all_in_level = False unheader_all_in_level = False
method = 0 method = 0
@ -395,123 +376,11 @@ def main():
return header_all_in_level, unheader_all_in_level return header_all_in_level, unheader_all_in_level
# Check for updates # Recurring lists logic
check_for_update(current_version)
# Initialise api
api, label_id = initialise(args)
# Start main loop def run_recurring_lists_logic(args, api, item, child_items_all):
while True:
start_time = time.time()
overview_item_ids = {}
overview_item_labels = {}
sync(api)
for project in api.projects.all():
# To determine if a sequential task was found
first_found_project = False
# Check if we need to (un)header entire project
header_all_in_p, unheader_all_in_p = check_header(project)
if label_id is not None:
# Get project type
project_type, project_type_changed = get_project_type(project)
logging.debug('Project \'%s\' being processed as %s',
project['name'], project_type)
# Get all items for the project
project_items = api.items.all(lambda x: x['project_id'] == project['id'])
# Run for both none-sectioned and sectioned items
for s in [0,1]:
if s == 0:
sections = [create_none_section()]
elif s == 1:
sections = api.sections.all(lambda x: x['project_id'] == project['id'])
for section in sections:
# Check if we need to (un)header entire secion
header_all_in_s, unheader_all_in_s = check_header(section)
# To determine if a sequential task was found
first_found_section = False
# Get section type
section_type, section_type_changed = get_section_type(
section)
logging.debug('Identified \'%s\' as %s type',
section['name'], section_type)
# Get all items for the section
items = [x for x in project_items if x['section_id'] == section['id']]
# Change top parents_id in order to sort later on
for item in items:
if not item['parent_id']:
item['parent_id'] = 0
# Sort by parent_id and filter for completable items
items = sorted(items, key=lambda x: (
x['parent_id'], x['child_order']))
# If a type has changed, clean label for good measure
if label_id is not None:
if project_type_changed == 1 or section_type_changed == 1:
# Remove labels
[remove_label(item, label_id) for item in items]
# Remove parent types
for item in items:
item['parent_type'] = None
# For all items in this section
for item in items:
active_type = None # Reset
# notes = api.notes.all() TODO: Quick notes test to see what the impact is?
# note_content = [x['content'] for x in notes if x['item_id'] == item['id']]
# print(note_content)
# Determine which child_items exist, both all and the ones that have not been checked yet
non_checked_items = list(
filter(lambda x: x['checked'] == 0, items))
child_items_all = list(
filter(lambda x: x['parent_id'] == item['id'], items))
child_items = list(
filter(lambda x: x['parent_id'] == item['id'], non_checked_items))
# Check if we need to (un)header entire item tree
header_all_in_i, unheader_all_in_i = check_header(item)
# Logic for applying and removing headers
if any([header_all_in_p, header_all_in_s, header_all_in_i]):
if item['content'][0] != '*':
item.update(content='* ' + item['content'])
for ci in child_items:
if not ci['content'].startswith('*'):
ci.update(content='* ' + ci['content'])
if any([unheader_all_in_p, unheader_all_in_s]):
if item['content'][0] == '*':
item.update(content=item['content'][2:])
if unheader_all_in_i:
[ci.update(content=ci['content'][2:]) for ci in child_items]
# Logic for recurring lists
if not args.recurring:
try:
# If old label is present, reset it
if item['r_tag'] == 1:
item['r_tag'] = 0
api.items.update(item['id'])
except:
pass
# If options turned on, start recurring lists logic
if args.recurring or args.end:
if item['parent_id'] == 0: if item['parent_id'] == 0:
try: try:
if item['due']['is_recurring']: if item['due']['is_recurring']:
@ -598,6 +467,128 @@ def main():
item['content']) item['content'])
pass pass
# Contains all main autodoist functionalities
def autodoist_magic(args, api, label_id):
# Preallocate dictionaries
overview_item_ids = {}
overview_item_labels = {}
for project in api.projects.all():
# To determine if a sequential task was found
first_found_project = False
# Check if we need to (un)header entire project
header_all_in_p, unheader_all_in_p = check_header(project)
if label_id is not None:
# Get project type
project_type, project_type_changed = get_project_type(
args, project)
logging.debug('Project \'%s\' being processed as %s',
project['name'], project_type)
# Get all items for the project
project_items = api.items.all(
lambda x: x['project_id'] == project['id'])
# Run for both none-sectioned and sectioned items
for s in [0, 1]:
if s == 0:
sections = [create_none_section()]
elif s == 1:
sections = api.sections.all(
lambda x: x['project_id'] == project['id'])
for section in sections:
# Check if we need to (un)header entire secion
header_all_in_s, unheader_all_in_s = check_header(section)
# To determine if a sequential task was found
first_found_section = False
# Get section type
section_type, section_type_changed = get_section_type(
args, section)
logging.debug('Identified \'%s\' as %s type',
section['name'], section_type)
# Get all items for the section
items = [x for x in project_items if x['section_id']
== section['id']]
# Change top parents_id in order to sort later on
for item in items:
if not item['parent_id']:
item['parent_id'] = 0
# Sort by parent_id and filter for completable items
items = sorted(items, key=lambda x: (
x['parent_id'], x['child_order']))
# If a type has changed, clean label for good measure
if label_id is not None:
if project_type_changed == 1 or section_type_changed == 1:
# Remove labels
[remove_label(item, label_id, overview_item_ids,
overview_item_labels) for item in items]
# Remove parent types
for item in items:
item['parent_type'] = None
# For all items in this section
for item in items:
active_type = None # Reset
# notes = api.notes.all() TODO: Quick notes test to see what the impact is?
# note_content = [x['content'] for x in notes if x['item_id'] == item['id']]
# print(note_content)
# Determine which child_items exist, both all and the ones that have not been checked yet
non_checked_items = list(
filter(lambda x: x['checked'] == 0, items))
child_items_all = list(
filter(lambda x: x['parent_id'] == item['id'], items))
child_items = list(
filter(lambda x: x['parent_id'] == item['id'], non_checked_items))
# Check if we need to (un)header entire item tree
header_all_in_i, unheader_all_in_i = check_header(item)
# Logic for applying and removing headers
if any([header_all_in_p, header_all_in_s, header_all_in_i]):
if item['content'][0] != '*':
item.update(content='* ' + item['content'])
for ci in child_items:
if not ci['content'].startswith('*'):
ci.update(content='* ' + ci['content'])
if any([unheader_all_in_p, unheader_all_in_s]):
if item['content'][0] == '*':
item.update(content=item['content'][2:])
if unheader_all_in_i:
[ci.update(content=ci['content'][2:])
for ci in child_items]
# Logic for recurring lists
if not args.recurring:
try:
# If old label is present, reset it
if item['r_tag'] == 1:
item['r_tag'] = 0
api.items.update(item['id'])
except:
pass
# If options turned on, start recurring lists logic
if args.recurring or args.end:
run_recurring_lists_logic(
args, api, item, child_items_all)
# If options turned on, start labelling logic # If options turned on, start labelling logic
if label_id is not None: if label_id is not None:
# Skip processing an item if it has already been checked or is a header # Skip processing an item if it has already been checked or is a header
@ -608,20 +599,23 @@ def main():
# Check item type # Check item type
item_type, item_type_changed = get_item_type( item_type, item_type_changed = get_item_type(
item, project_type) args, item, project_type)
logging.debug('Identified \'%s\' as %s type', logging.debug('Identified \'%s\' as %s type',
item['content'], item_type) item['content'], item_type)
# Determine hierarchy types for logic # Determine hierarchy types for logic
hierarchy_types = [item_type, section_type, project_type] hierarchy_types = [item_type,
active_types = [type(x) != type(None) for x in hierarchy_types] section_type, project_type]
active_types = [type(x) != type(None)
for x in hierarchy_types]
# If it is a parentless task # If it is a parentless task
if item['parent_id'] == 0: if item['parent_id'] == 0:
if active_types[0]: if active_types[0]:
# Do item types # Do item types
active_type = item_type active_type = item_type
add_label(item, label_id) add_label(
item, label_id, overview_item_ids, overview_item_labels)
elif active_types[1]: elif active_types[1]:
# Do section types # Do section types
@ -629,10 +623,12 @@ def main():
if section_type == 'sequential' or section_type == 's-p': if section_type == 'sequential' or section_type == 's-p':
if not first_found_section: if not first_found_section:
add_label(item, label_id) add_label(
item, label_id, overview_item_ids, overview_item_labels)
first_found_section = True first_found_section = True
elif section_type == 'parallel' or section_type == 'p-s': elif section_type == 'parallel' or section_type == 'p-s':
add_label(item, label_id) add_label(
item, label_id, overview_item_ids, overview_item_labels)
elif active_types[2]: elif active_types[2]:
# Do project types # Do project types
@ -640,11 +636,13 @@ def main():
if project_type == 'sequential' or project_type == 's-p': if project_type == 'sequential' or project_type == 's-p':
if not first_found_project: if not first_found_project:
add_label(item, label_id) add_label(
item, label_id, overview_item_ids, overview_item_labels)
first_found_project = True first_found_project = True
elif project_type == 'parallel' or project_type == 'p-s': elif project_type == 'parallel' or project_type == 'p-s':
add_label(item, label_id) add_label(
item, label_id, overview_item_ids, overview_item_labels)
# Mark other conditions too # Mark other conditions too
if first_found_section == False and active_types[1]: if first_found_section == False and active_types[1]:
@ -656,7 +654,7 @@ def main():
if len(child_items) > 0: if len(child_items) > 0:
# Check if item state has changed, if so clean children for good measure # Check if item state has changed, if so clean children for good measure
if item_type_changed == 1: if item_type_changed == 1:
[remove_label(child_item, label_id) [remove_label(child_item, label_id, overview_item_ids, overview_item_labels)
for child_item in child_items] for child_item in child_items]
# Process sequential tagged items (item_type can overrule project_type) # Process sequential tagged items (item_type can overrule project_type)
@ -666,30 +664,38 @@ def main():
child_item['parent_type'] = active_type child_item['parent_type'] = active_type
# Pass label down to the first child # Pass label down to the first child
if child_item['checked'] == 0 and label_id in item['labels']: if child_item['checked'] == 0 and label_id in item['labels']:
add_label(child_item, label_id) add_label(
remove_label(item, label_id) child_item, label_id, overview_item_ids, overview_item_labels)
remove_label(
item, label_id, overview_item_ids, overview_item_labels)
else: else:
# Clean for good measure # Clean for good measure
remove_label(child_item, label_id) remove_label(
child_item, label_id, overview_item_ids, overview_item_labels)
# Process parallel tagged items or untagged parents # Process parallel tagged items or untagged parents
elif active_type == 'parallel' or (active_type == 's-p' and label_id in item['labels']): elif active_type == 'parallel' or (active_type == 's-p' and label_id in item['labels']):
remove_label(item, label_id) remove_label(
item, label_id, overview_item_ids, overview_item_labels)
for child_item in child_items: for child_item in child_items:
child_item['parent_type'] = active_type child_item['parent_type'] = active_type
if child_item['checked'] == 0: if child_item['checked'] == 0:
# child_first_found = True # child_first_found = True
add_label(child_item, label_id) add_label(
child_item, label_id, overview_item_ids, overview_item_labels)
# Remove labels based on start / due dates # Remove labels based on start / due dates
# If item is too far in the future, remove the next_action tag and skip # If item is too far in the future, remove the next_action tag and skip
try: try:
if args.hide_future > 0 and 'due' in item.data and item['due'] is not None: if args.hide_future > 0 and 'due' in item.data and item['due'] is not None:
due_date = datetime.strptime(item['due']['date'], "%Y-%m-%d") due_date = datetime.strptime(
future_diff = (due_date - datetime.today()).days item['due']['date'], "%Y-%m-%d")
future_diff = (
due_date - datetime.today()).days
if future_diff >= args.hide_future: if future_diff >= args.hide_future:
remove_label(item, label_id) remove_label(
item, label_id, overview_item_ids, overview_item_labels)
continue continue
except: except:
# Hide-future not set, skip # Hide-future not set, skip
@ -702,16 +708,21 @@ def main():
if f1 > -1 and f2 == -1: if f1 > -1 and f2 == -1:
f_end = item['content'][f1+6:].find(' ') f_end = item['content'][f1+6:].find(' ')
if f_end > -1: if f_end > -1:
start_date = item['content'][f1+6:f1+6+f_end] start_date = item['content'][f1 +
6:f1+6+f_end]
else: else:
start_date = item['content'][f1+6:] start_date = item['content'][f1+6:]
# If start-date hasen't passed, remove all labels # If start-date hasen't passed, remove all labels
start_date = datetime.strptime(start_date , args.dateformat) start_date = datetime.strptime(
future_diff = (datetime.today()-start_date).days start_date, args.dateformat)
future_diff = (
datetime.today()-start_date).days
if future_diff < 0: if future_diff < 0:
remove_label(item, label_id) remove_label(
[remove_label(child_item, label_id) for child_item in child_items] item, label_id, overview_item_ids, overview_item_labels)
[remove_label(child_item, label_id, overview_item_ids,
overview_item_labels) for child_item in child_items]
continue continue
except: except:
@ -723,8 +734,10 @@ def main():
try: try:
f = item['content'].find('start=due-') f = item['content'].find('start=due-')
if f > -1: if f > -1:
f1a = item['content'].find('d') # Find 'd' from 'due' f1a = item['content'].find(
f1b = item['content'].rfind('d') # Find 'd' from days 'd') # Find 'd' from 'due'
f1b = item['content'].rfind(
'd') # Find 'd' from days
f2 = item['content'].find('w') f2 = item['content'].find('w')
f_end = item['content'][f+10:].find(' ') f_end = item['content'][f+10:].find(' ')
@ -735,9 +748,11 @@ def main():
try: try:
item_due_date = item['due']['date'] item_due_date = item['due']['date']
item_due_date = datetime.strptime(item_due_date, '%Y-%m-%d') item_due_date = datetime.strptime(
item_due_date, '%Y-%m-%d')
except: except:
logging.warning('No due date to determine start date for item: "%s".', item['content']) logging.warning(
'No due date to determine start date for item: "%s".', item['content'])
continue continue
if f1a != f1b and f1b > -1: # To make sure it doesn't trigger if 'w' is chosen if f1a != f1b and f1b > -1: # To make sure it doesn't trigger if 'w' is chosen
@ -747,10 +762,13 @@ def main():
# If we're not in the offset from the due date yet, remove all labels # If we're not in the offset from the due date yet, remove all labels
start_date = item_due_date - td start_date = item_due_date - td
future_diff = (datetime.today()-start_date).days future_diff = (
datetime.today()-start_date).days
if future_diff < 0: if future_diff < 0:
remove_label(item, label_id) remove_label(
[remove_label(child_item, label_id) for child_item in child_items] item, label_id, overview_item_ids, overview_item_labels)
[remove_label(child_item, label_id, overview_item_ids,
overview_item_labels) for child_item in child_items]
continue continue
except: except:
@ -758,9 +776,85 @@ def main():
'Wrong start-date format for item: %s. Please use "start=due-<NUM><d or w>"', item['content']) 'Wrong start-date format for item: %s. Please use "start=due-<NUM><d or w>"', item['content'])
continue continue
return overview_item_ids, overview_item_labels
# Main function
def main():
# Version
current_version = 'v1.4.1'
# Main process functions.
parser = argparse.ArgumentParser(
formatter_class=make_wide(argparse.HelpFormatter, w=110, h=50))
parser.add_argument('-a', '--api_key',
help='Takes your Todoist API Key.', type=str)
parser.add_argument(
'-l', '--label', help='Enable next action labelling. Define which label to use.', type=str)
parser.add_argument(
'-r', '--recurring', help='Enable regeneration of sub-tasks in recurring lists.', action='store_true')
parser.add_argument(
'-e', '--end', help='Enable alternative end-of-day time instead of default midnight. Enter a number from 1 to 24 to define which hour is used.', type=int)
parser.add_argument(
'-d', '--delay', help='Specify the delay in seconds between syncs (default 5).', default=5, type=int)
parser.add_argument(
'-pp', '--pp_suffix', help='Change suffix for parallel-parallel labeling (default "//").', default='//')
parser.add_argument(
'-ss', '--ss_suffix', help='Change suffix for sequential-sequential labeling (default "--").', default='--')
parser.add_argument(
'-ps', '--ps_suffix', help='Change suffix for parallel-sequential labeling (default "/-").', default='/-')
parser.add_argument(
'-sp', '--sp_suffix', help='Change suffix for sequential-parallel labeling (default "-/").', default='-/')
parser.add_argument(
'-df', '--dateformat', help='Strptime() format of starting date (default "%%d-%%m-%%Y").', default='%d-%m-%Y')
parser.add_argument(
'-hf', '--hide_future', help='Prevent labelling of future tasks beyond a specified number of days.', default=0, type=int)
parser.add_argument(
'--onetime', help='Update Todoist once and exit.', action='store_true')
parser.add_argument(
'--nocache', help='Disables caching data to disk for quicker syncing.', action='store_true')
parser.add_argument('--debug', help='Enable detailed debugging in log.',
action='store_true')
parser.add_argument('--inbox', help='The method the Inbox should be processed with.',
default=None, choices=['parallel', 'sequential'])
args = parser.parse_args()
# Set debug
if args.debug:
log_level = logging.DEBUG
else:
log_level = logging.INFO
logging.basicConfig(level=log_level,
format='%(asctime)s %(levelname)-8s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
handlers=[logging.FileHandler(
'debug.log', 'w+', 'utf-8'),
logging.StreamHandler()]
)
# Check for updates
check_for_update(current_version)
# Initialise api
api, label_id = initialise(args)
# Start main loop
while True:
start_time = time.time()
sync(api)
# Evaluate projects, sections, and items
overview_item_ids, overview_item_labels = autodoist_magic(
args, api, label_id)
# Commit the queue with changes # Commit the queue with changes
if label_id is not None: if label_id is not None:
update_labels(label_id) update_labels(api, label_id, overview_item_ids,
overview_item_labels)
if len(api.queue): if len(api.queue):
len_api_q = len(api.queue) len_api_q = len(api.queue)
@ -783,11 +877,13 @@ def main():
delta_time = end_time - start_time delta_time = end_time - start_time
if args.delay - delta_time < 0: if args.delay - delta_time < 0:
logging.debug('Computation time %d is larger than the specified delay %d. Sleeping skipped.', delta_time, args.delay) logging.debug(
'Computation time %d is larger than the specified delay %d. Sleeping skipped.', delta_time, args.delay)
elif args.delay >= 0: elif args.delay >= 0:
sleep_time = args.delay - delta_time sleep_time = args.delay - delta_time
logging.debug('Sleeping for %d seconds', sleep_time) logging.debug('Sleeping for %d seconds', sleep_time)
time.sleep(sleep_time) time.sleep(sleep_time)
if __name__ == '__main__': if __name__ == '__main__':
main() main()