Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions gtd.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ def __init__(self, config: Configuration):
self.connection = TrelloConnection(config)
self.display = Display(config, self.connection)
self.board = self.connection.main_board()
self.checklists = self.board.get_checklists()

# Cached state for card_repl
self._list_choices = build_name_lookup(self.connection.main_board().get_lists('open'))
self._label_choices = build_name_lookup(self.connection.main_board().get_labels(limit=200))
Expand Down Expand Up @@ -88,6 +90,7 @@ def card_repl(self, card: dict) -> bool:
'delete': 'permanently delete this card',
'duedate': 'add a due date or change the due date',
'description': 'change the description of this card (desc)',
'checklists': 'change the checklists of this card (check)',
'help': 'display this help output (h)',
'move': 'move to a different board and list (m)',
'next': 'move to the next card (n)',
Expand Down Expand Up @@ -117,6 +120,8 @@ def card_repl(self, card: dict) -> bool:
webbrowser.open(url)
elif user_input in ['desc', 'description']:
card.change_description()
elif user_input in ['check', 'checklists']:
card.change_checklists()
elif user_input == 'delete':
card.delete()
print('Card deleted')
Expand Down
43 changes: 43 additions & 0 deletions todo/card.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
import arrow
import click
import trello
import time

from prompt_toolkit import prompt
from prompt_toolkit.validation import Validator
from prompt_toolkit.completion import WordCompleter, FuzzyWordCompleter

from todo.connection import TrelloConnection
from todo.checklist_handler import ChecklistHandler
from todo.exceptions import GTDException
from todo.input import prompt_for_confirmation, single_select
from todo.misc import get_title_of_webpage, DevNullRedirect, VALID_URL_REGEX, return_on_eof, build_name_lookup
Expand Down Expand Up @@ -61,6 +64,14 @@ def id(self):
def fetch(self):
'''Refresh the base card JSON structure'''
self.card_json = self.connection.trello.fetch_json('/cards/' + self.id, query_params={'fields': 'all'})
checklists = self.connection.main_board().get_checklists()

this_checklist = []
for id in self.card_json['idChecklists']:
for item in checklists:
if id == item.id:
this_checklist.append(item)
self.card_json['Checklists'] = this_checklist

def fetch_comments(self, force: bool = False):
'''Fetch the comments on this card and return them in JSON format, adding them into self.card_json
Expand Down Expand Up @@ -292,6 +303,30 @@ def change_description(self):
)
return new_desc

def change_checklists(self):
old_checklists = self.card_json['Checklists']

checklist_handling = ChecklistHandler(connection=self.connection, id=self.id, checklists=old_checklists)
checklists_to_edit = checklist_handling.parse_checklists()
success = False
editor_content = checklists_to_edit
while not success:
new_checklists_edited = click.edit(text=editor_content)

if new_checklists_edited == checklists_to_edit:
# no change done
return
elif new_checklists_edited is None:
# no save in editor
# discard changes if editing failed
return
new_checklists, success = checklist_handling.parse_edited_checklists(new_checklists_edited=new_checklists_edited)
editor_content = new_checklists_edited
self.card_json['Checklists'] = new_checklists
checklist_handling.remove_old_checklists()

return new_checklists


def search_for_regex(card, title_regex, regex_flags):
try:
Expand Down Expand Up @@ -397,6 +432,8 @@ def create(context, **kwargs):
cards_json = context.connection.trello.fetch_json(f'/boards/{context.board.id}', query_params=query_params)
target_cards.extend(cards_json['cards'])

checklists = context.board.get_checklists()

# Post-process the returned JSON, filtering down to the other passed parameters
filters = []
post_processed_cards = []
Expand All @@ -416,6 +453,12 @@ def create(context, **kwargs):

for card in target_cards:
if all(filter_func(card) for filter_func in filters):
this_checklist = []
for id in card['idChecklists']:
for item in checklists:
if id == item.id:
this_checklist.append(item)
card['Checklists'] = this_checklist
post_processed_cards.append(card)

if not post_processed_cards:
Expand Down
132 changes: 132 additions & 0 deletions todo/checklist_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
from todo.connection import TrelloConnection
import yaml
import trello
import time
import re
from tqdm import tqdm


class Checklist(object):
def __init__(self, id, connection, name, items, checks):
self.id = id
self.connection = connection
self.name = name
self.items = items
self.checks = checks

def add_to_trello(self):
json_obj = self.connection.trello.fetch_json(
'/cards/' + self.id + '/checklists',
http_method='POST',
post_args={'name': self.name}, )
cl = trello.checklist.Checklist(self.connection.trello, [], json_obj, trello_card=self.id)
for text, checked in zip(self.items, self.checks):
cl.add_checklist_item(text, checked=checked)
return cl


class ChecklistHandler:
def __init__(self, connection: TrelloConnection, id, checklists):
self.connection = connection
self.id = id
self.checklists = checklists
self.new_checklist = None

def parse_checklists(self):
checklists_to_edit = ""
for checklist in self.checklists:
items = []
for item in checklist.items:
if item['state'] == 'complete':
items.append("[x] " + item['name'])
elif item['state'] == 'incomplete':
items.append("[ ] " + item['name'])
yaml_checklist = {checklist.name.replace("\n", ""): items}
checklists_to_edit += yaml.dump(yaml_checklist, default_flow_style=False) + "\n"
return checklists_to_edit


def parse_item_string(self, line):
line = line.lstrip()
line = line[1:].split("] ")
if line[0] == "x" or line[0] == "X":
return line[1], True
elif line[0] == " ":
return line[1], False

def create_objects_from_list(self, l_checklists):
checklist_objects = []
for checklist in l_checklists:
name = list(checklist.keys())[0]
checklist_items = list(checklist.values())[0]
checks = []
texts = []
for item in checklist_items:
text, checked = self.parse_item_string(item)
checks.append(checked)
texts.append(text)
this_checklist = Checklist(id=self.id, connection=self.connection, name=name, items=texts, checks=checks)
checklist_objects.append(this_checklist)
return checklist_objects

def parse_edited_checklists(self, new_checklists_edited):
try:
cleaned_checklist_string = self.clean_checklist_string(new_checklists_edited)
l_checklists = [yaml.safe_load(item) for item in cleaned_checklist_string.split("\n\n")]
checklist_objects = self.create_objects_from_list(l_checklists=l_checklists)
print("Parsing successful!")
except AttributeError:
print("Cannot parse the checklists from editor! YAML file not valid!")
print("Opening editor again")
time.sleep(3)
return None, False
except yaml.scanner.ScannerError:
print("ScannerError! YAML file not valid!")
print("Opening editor again")
time.sleep(3)
return None, False
except:
print("Unknown error!")
print("Opening editor again")
time.sleep(3)
return None, False

new_checklists = []
print("Adjusting checklists...")
for checklist in tqdm(checklist_objects):
cl = checklist.add_to_trello()
new_checklists.append(cl)
return new_checklists, True

def clean_checklist_string(self, checklist_string):
cleaned_checklist_string = checklist_string

while cleaned_checklist_string.endswith("\n"):
cleaned_checklist_string = cleaned_checklist_string[:-1]

while cleaned_checklist_string.startswith("\n"):
cleaned_checklist_string = cleaned_checklist_string[1:]

while cleaned_checklist_string != cleaned_checklist_string.replace("\n\n\n", "\n\n"):
cleaned_checklist_string = cleaned_checklist_string.replace("\n\n\n", "\n\n")

while cleaned_checklist_string != cleaned_checklist_string.replace("]\n\n", "]\n"):
cleaned_checklist_string = cleaned_checklist_string.replace("]\n\n", "]\n")

while cleaned_checklist_string != cleaned_checklist_string.replace("\t", " "):
cleaned_checklist_string = cleaned_checklist_string.replace("\t", " ")

while cleaned_checklist_string != cleaned_checklist_string.replace("[]", "[ ]"):
cleaned_checklist_string = cleaned_checklist_string.replace("[]", "[ ]")

cleaned_checklist_string = re.sub(r"\n\n(\s+)\[", r"\n [", cleaned_checklist_string)
cleaned_checklist_string = re.sub(r"](\s+)(\w)", r"] \2", cleaned_checklist_string)
cleaned_checklist_string = re.sub(r"\[(\w)\](\w)", r"[\1]] \2", cleaned_checklist_string)
cleaned_checklist_string = re.sub(r"\n\n-", r"\n-", cleaned_checklist_string)

return cleaned_checklist_string

def remove_old_checklists(self):
print("Cleaning checklists...")
for old_checklist in tqdm(self.checklists):
old_checklist.delete()
1 change: 1 addition & 0 deletions todo/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def main_board(self):
board_json = self.boards[0]
board_object = trello.Board.from_json(self.trello, json_obj=board_json)
self._main_board = board_object

return board_object

def boards_by_name(self):
Expand Down
9 changes: 9 additions & 0 deletions todo/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,12 @@ def show_card(self, card: dict):
indent_print('Description', '')
for line in card['desc'].splitlines():
print(' ' * 4 + line)

indent_print('Checklists', '')
for checklist in card['Checklists']:
print(' ' * 4 + checklist.name + ":")
for item in checklist.items:
if item['state'] == 'complete':
print(' ' * 6 + "[x] " + item['name'])
elif item['state'] == 'incomplete':
print(' ' * 6 + "[ ] " + item['name'])