def add_labels(self, label_choices): '''Give the user a way to toggle labels on this card by their name rather than by a numeric selection interface. Using prompt_toolkit, we have automatic completion which makes things substantially faster without having to do a visual lookup against numeric IDs Options: label_choices: str->trello.Label, the names and objects of labels on this board ''' print('Enter a tag name to toggle it, <TAB> completes. Ctrl+D to exit') while True: label_completer = FuzzyWordCompleter(label_choices.keys()) userinput = prompt('gtd.py > tag > ', completer=label_completer).strip() if userinput not in label_choices.keys(): if prompt_for_confirmation( f'Unrecognized tag name {userinput}, would you like to create it?', False): label = self.connection.main_board().add_label( userinput, 'green') self.add_label(label.id) click.echo( f'Added tag {label.name} to board {self.connection.main_board().name} and to the card {self}' ) label_choices[userinput] = label else: label_obj = label_choices[userinput] try: self.add_label(label_obj.id) click.secho(f'Added tag {userinput}', fg='green') except trello.exceptions.ResourceUnavailable: # This label already exists on the card so remove it self.remove_label(label_obj.id) click.secho(f'Removed tag {userinput}', fg='red')
def delete_cards(config, force, noninteractive, tags, no_tags, match, listname, attachments, has_due): '''Delete a set of cards specified ''' _, board = BoardTool.start(config) display = Display(config.color) if config.banner and not json: display.banner() cards = BoardTool.filter_cards( board, tags=tags, no_tags=no_tags, title_regex=match, list_regex=listname, has_attachments=attachments, has_due_date=has_due ) method = 'delete' if force else 'archive' if noninteractive: if force: [c.delete() for c in cards] else: [c.set_closed(True) for c in cards] else: for card in cards: display.show_card(card) if prompt_for_confirmation('Delete this card?'): if force: card.delete() else: card.set_closed(True) click.secho('Card {}d!'.format(method), fg='red')
def delete_cards(config, force, noninteractive, tags, no_tags, match, listname, attachments, has_due): '''Delete a set of cards specified ''' _, board, _ = BoardTool.start(config) display = Display(config.color) if config.banner and not json: display.banner() cards = BoardTool.filter_cards( board, tags=tags, no_tags=no_tags, title_regex=match, list_regex=listname, has_attachments=attachments, has_due_date=has_due ) method = 'delete' if force else 'archive' if noninteractive: if force: [c.delete() for c in cards] else: [c.set_closed(True) for c in cards] else: for card in cards: display.show_card(card) if prompt_for_confirmation('Delete this card?'): if force: card.delete() else: card.set_closed(True) click.secho('Card {}d!'.format(method), fg='red')
def delete_cards(ctx, force, noninteractive, tags, no_tags, match, listname, attachments, has_due, status): '''Delete a set of cards specified ''' ctx.display.banner() cards = CardView.create( ctx, status=status, tags=tags, no_tags=no_tags, title_regex=match, list_regex=listname, has_attachments=attachments, has_due_date=has_due, ) method = 'delete' if force else 'archive' if noninteractive: if force: [c.delete() for c in cards] else: [c.set_closed(True) for c in cards] else: for card in cards: ctx.display.show_card(card) if prompt_for_confirmation('Delete this card?'): if force: card.delete() else: card.set_closed(True) click.secho(f'Card {method}d!', fg='red')
def batch_attach(ctx): '''Extract HTTP links from card titles''' cards = CardView.create(ctx, status='visible', title_regex=VALID_URL_REGEX) ctx.display.banner() for card in cards: ctx.display.show_card(card) if prompt_for_confirmation('Attach title?', True): card.title_to_link()
def delete_tag(ctx, name, noninteractive): '''Delete a tag by name''' tags = [l for l in ctx.board.get_labels() if l.name == name] if not tags: click.secho(f'No such tag {name}', fg='red') for t in tags: if noninteractive or prompt_for_confirmation( f'Delete tag "{t.name}"?'): ctx.board.delete_label(t.id) click.secho(f'Deleted {t.name}!', fg='green')
def delete_list(ctx, name, noninteractive): '''Delete a list by name''' lists = [l for l in ctx.board.get_lists('open') if l.name == name] if not lists: click.secho(f'No such list {name}', fg='red') for l in lists: if noninteractive or prompt_for_confirmation( f'Close list "{l.name}"?'): l.close() click.secho(f'Closed {l.name}!', fg='green')
def batch_attach(config): '''Extract HTTP links from card titles''' connection, board = BoardTool.start(config) cards = BoardTool.filter_cards(board, title_regex=VALID_URL_REGEX) display = Display(config.color) if config.banner: display.banner() for card in cards: display.show_card(card) if prompt_for_confirmation('Attach title?', True): CardTool.title_to_link(card)
def delete_list(config, name, noninteractive): '''Delete a list by name''' _, board = BoardTool.start(config) lists = [l for l in board.get_lists('open') if l.name == name] if not lists: click.secho('No such list {}'.format(name), fg='red') for l in lists: if noninteractive or prompt_for_confirmation('Close list "{}"?'.format( l.name)): l.close() click.secho('Closed {}!'.format(l.name), fg='green')
def delete_tag(config, name, noninteractive): '''Delete a tag by name''' _, board = BoardTool.start(config) tags = [l for l in board.get_labels() if l.name == name] if not tags: click.secho('No such tag {}'.format(name), fg='red') for t in tags: if noninteractive or prompt_for_confirmation('Delete tag "{}"?'.format( t.name)): board.delete_label(t.id) click.secho('Deleted {}!'.format(t.name), fg='green')
def delete_lists(config, name, noninteractive): '''Delete lists containing the substring <name> ''' _, board, _ = BoardTool.start(config) lists = [l for l in board.get_lists('open') if name in l.name] if noninteractive: [l.set_closed() for l in lists] else: for l in lists: if prompt_for_confirmation('Close this list?'): l.set_closed() click.secho('Closed!', fg='green')
def delete_lists(config, name, noninteractive): '''Delete lists containing the substring <name> ''' _, board = BoardTool.start(config) lists = [l for l in board.get_lists('open') if name in l.name] if noninteractive: [l.set_closed() for l in lists] else: for l in lists: if prompt_for_confirmation('Close this list?'): l.set_closed() click.secho('Closed!', fg='green')
def batch_move(config, tags, no_tags, match, listname, attachments, has_due): '''Change the list of each card selected''' connection, board = BoardTool.start(config) cards = BoardTool.filter_cards(board, tags=tags, no_tags=no_tags, title_regex=match, list_regex=listname, has_attachments=attachments, has_due_date=has_due) display = Display(config.color) if config.banner: display.banner() for card in cards: display.show_card(card) if prompt_for_confirmation('Want to move this one?', True): CardTool.move_to_list(card)
def batch_due(config, tags, no_tags, match, listname, attachments, has_due): '''Set due date for all cards selected''' connection, board = BoardTool.start(config) cards = BoardTool.filter_cards(board, tags=tags, no_tags=no_tags, title_regex=match, list_regex=listname, has_attachments=attachments, has_due_date=has_due) display = Display(config.color) if config.banner: display.banner() for card in cards: display.show_card(card) if prompt_for_confirmation('Set due date?'): CardTool.set_due_date(card)
def batch_due(ctx, tags, no_tags, match, listname, attachments, has_due, status): '''Set due date for all cards selected''' cards = CardView.create( ctx, status=status, tags=tags, no_tags=no_tags, title_regex=match, list_regex=listname, has_attachments=attachments, has_due_date=has_due, ) ctx.display.banner() for card in cards: ctx.display.show_card(card) if prompt_for_confirmation('Set due date?'): card.set_due_date()
def batch_move(ctx, tags, no_tags, match, listname, attachments, has_due, status): '''Change the list of each card selected''' cards = CardView.create( ctx, status=status, tags=tags, no_tags=no_tags, title_regex=match, list_regex=listname, has_attachments=attachments, has_due_date=has_due, ) ctx.display.banner() for card in cards: ctx.display.show_card(card) if prompt_for_confirmation('Want to move this one?', True): card.move_to_list(ctx._list_choices)
def batch_due(config, tags, no_tags, match, listname, attachments, has_due): '''Set due date for all cards selected''' connection, board = BoardTool.start(config) cards = BoardTool.filter_cards( board, tags=tags, no_tags=no_tags, title_regex=match, list_regex=listname, has_attachments=attachments, has_due_date=has_due ) display = Display(config.color) if config.banner: display.banner() for card in cards: display.show_card(card) if prompt_for_confirmation('Set due date?'): CardTool.set_due_date(card)
def batch_move(config, tags, no_tags, match, listname, attachments, has_due): '''Change the list of each card selected''' connection, board = BoardTool.start(config) cards = BoardTool.filter_cards( board, tags=tags, no_tags=no_tags, title_regex=match, list_regex=listname, has_attachments=attachments, has_due_date=has_due ) display = Display(config.color) if config.banner: display.banner() for card in cards: display.show_card(card) if prompt_for_confirmation('Want to move this one?', True): CardTool.move_to_list(card)
def card_repl(self, card: dict) -> bool: '''card_repl displays a command-prompt based UI for modifying a card, with tab-completion and suggestions. It is the logic behind "gtd review" and the "-e" flag in "gtd add" It makes assumptions about what a user might want to do with a card: - Are there attachments? Maybe you want to open them. - Does there appear to be a URL in the title? You might want to attach it. - Are there no tags? Maybe you want to add some. Returns: boolean: move forwards or backwards in the deck of cards ''' on = Colors.yellow if self.config.color else '' off = Colors.reset if self.config.color else '' self.display.show_card(card) if self.config.prompt_for_open_attachments and card['badges'][ 'attachments']: if prompt_for_confirmation(f'{on}Open attachments?{off}', False): with DevNullRedirect(): for url in [ a['url'] for a in card.fetch_attachments() if a['url'] ]: webbrowser.open(url) if re.search(VALID_URL_REGEX, card['name']): if prompt_for_confirmation( f'{on}Link in title detected, want to attach it & rename?{off}', True): card.title_to_link() if self.config.prompt_for_untagged_cards and not card['labels']: print(f'{on}No tags on this card yet, want to add some?{off}') card.add_labels(self._label_choices) commands = { 'archive': 'mark this card as closed', 'attach': 'add, delete, or open attachments', 'change-list': 'move this to a different list on the same board', 'comment': 'add a comment to this card', 'delete': 'permanently delete this card', 'duedate': 'add a due date or change the due date', 'description': 'change the description of this card (desc)', 'help': 'display this help output (h)', 'move': 'move to a different board and list (m)', 'next': 'move to the next card (n)', 'open': 'open all links on this card (o)', 'prev': 'go back to the previous card (p)', 'print': 're-display this card', 'rename': 'change title of this card', 'tag': 'add or remove tags on this card (t)', 'unarchive': 'mark this card as open', 'quit': 'exit program', } command_completer = FuzzyWordCompleter(commands.keys()) while True: user_input = prompt('gtd.py > ', completer=command_completer) if user_input in ['q', 'quit']: raise GTDException(0) elif user_input in ['n', 'next']: return True elif user_input in ['p', 'prev']: return False elif user_input == 'print': card.fetch() self.display.show_card(card) elif user_input in ['o', 'open']: with DevNullRedirect(): for url in [ a['url'] for a in card.fetch_attachments() if a['url'] is not None ]: webbrowser.open(url) elif user_input in ['desc', 'description']: card.change_description() elif user_input == 'delete': card.delete() print('Card deleted') return True elif user_input == 'attach': card.manipulate_attachments() elif user_input == 'archive': card.set_closed(True) print('Card archived') return True elif user_input == 'unarchive': card.set_closed(False) print('Card returned to board') elif user_input in ['t', 'tag']: card.add_labels(self._label_choices) elif user_input == 'rename': # TODO optional form 'rename New name of card' card.rename() elif user_input == 'duedate': card.set_due_date() elif user_input in ['h', 'help']: for cname, cdesc in commands.items(): print('{0:<16}| {1}{2}{3}'.format(cname, on, cdesc, off)) elif user_input == 'change-list': if card.move_to_list(self._list_choices): return True elif user_input in ['m', 'move']: self.move_between_boards(card) elif user_input == 'comment': # TODO Optional form 'comment Contents of a comment' new_comment = click.edit(text='<Comment here>', require_save=True) if new_comment: card.comment(new_comment) else: click.secho('Change the text & save to post the comment', fg='red') else: print( f'{on}{user_input}{off} is not a command, type "{on}help{off}" to view available commands' )