Пример #1
0
def add_card(config, title, message, edit, listname):
    '''Add a new card. If no title is provided, $EDITOR will be opened so you can write one.'''
    connection, board = BoardTool.start(config)
    if listname is None:
        inbox = BoardTool.get_inbox_list(connection, config)
    else:
        pattern = re.compile(listname, flags=re.I)
        target_lists = filter(lambda x: pattern.search(x.name),
                              board.get_lists('open'))
        try:
            inbox = next(target_lists)
        except StopIteration:
            click.secho('No list names matched by {}'.format(listname),
                        fg='red')
            raise GTDException(1)
    if not title:
        title = click.edit(require_save=True, text='<Title here>')
        if title is None:  # No changes were made in $EDITOR
            click.secho('No title entered for the new card!', fg='red')
            raise GTDException(1)
        else:
            title = title.strip()
    returned = inbox.add_card(name=title, desc=message)
    if edit:
        display = Display(config.color)
        list_lookup = BoardTool.list_lookup(board)
        label_lookup = BoardTool.label_lookup(board)
        CardTool.smart_menu(returned,
                            display.show_card,
                            list_lookup,
                            label_lookup,
                            color=config.color)
    else:
        click.secho('Successfully added card {0}!'.format(returned),
                    fg='green')
Пример #2
0
def add_card(ctx, title, message, edit, listname):
    '''Add a new card. If no title is provided, $EDITOR will be opened so you can write one.'''
    connection = ctx.connection
    if listname is None:
        inbox = connection.inbox_list()
    else:
        pattern = re.compile(listname, flags=re.I)
        target_lists = filter(lambda x: pattern.search(x.name),
                              ctx.board.get_lists('open'))
        try:
            inbox = next(target_lists)
        except StopIteration:
            click.secho(f'No list names matched by {listname}', fg='red')
            raise GTDException(1)
    if not title:
        title = click.edit(require_save=True, text='<Title here>')
        if title is None:  # No changes were made in $EDITOR
            click.secho('No title entered for the new card!', fg='red')
            raise GTDException(1)
        else:
            title = title.strip()
    returned = inbox.add_card(name=title, desc=message)
    if edit:
        ctx.card_repl(returned)
    else:
        click.secho(f'Successfully added card "{returned.name}"!', fg='green')
Пример #3
0
def grep(config, pattern, insensitive, count, regexp):
    '''egrep through titles of cards on this board. This command attemps to replicate a couple of grep flags
    faithfully, so if you're a power-user of grep this command will feel familiar.
    '''
    if not (pattern or regexp):
        click.secho('No pattern provided to grep: use either the argument or -e', fg='red')
        raise GTDException(1)
    # Merge together the different regex arguments
    final_pattern = '|'.join(regexp) if regexp else ''
    if pattern and final_pattern:
        final_pattern = final_pattern + '|' + pattern
    elif pattern:
        final_pattern = pattern
    flags = re.I if insensitive else 0
    connection, board = BoardTool.start(config)
    cards = BoardTool.filter_cards(
        board,
        title_regex=final_pattern,
        regex_flags=flags
    )
    if count:
        print(sum(1 for _ in cards))
        raise GTDException(0)
    display = Display(config.color)
    if config.banner:
        display.banner()
    display.show_cards(cards)
Пример #4
0
 def __connect(self, config):
     trello_client = self.initialize_trello(config)
     try:
         # This is the first connection to the API made by the client
         self.boards = trello_client.list_boards()
         return trello_client
     except requests.exceptions.ConnectionError:
         print('[FATAL] Could not connect to the Trello API!')
         raise GTDException(1)
     except trello.exceptions.Unauthorized:
         print('[FATAL] Trello API credentials are invalid')
         raise GTDException(1)
Пример #5
0
 def __connect(self, config):
     trello_client = self.initialize_trello(config)
     try:
         # A simple API call (data reused in BoardTool.get_main_board) to initiate connection & test our credentials etc
         self.boards = trello_client.fetch_json('/members/me/boards/?filter=open')
         return trello_client
     except requests.exceptions.ConnectionError:
         print('[FATAL] Could not connect to the Trello API!')
         raise GTDException(1)
     except trello.exceptions.Unauthorized:
         print('[FATAL] Trello API credentials are invalid')
         raise GTDException(1)
Пример #6
0
def grep(ctx, pattern, insensitive, count, regexp, by, fields, use_json):
    '''egrep through titles of cards on this board. This command attemps to replicate a couple of grep flags
    faithfully, so if you're a power-user of grep this command will feel familiar.
    One deviation from grep is the --json flag, which outputs all matching cards in full JSON format.
    '''
    if not (pattern or regexp):
        click.secho(
            'No pattern provided to grep: use either the argument or -e',
            fg='red')
        raise GTDException(1)
    # Merge together the different regex arguments
    final_pattern = '|'.join(regexp) if regexp else ''
    if pattern and final_pattern:
        final_pattern = final_pattern + '|' + pattern
    elif pattern:
        final_pattern = pattern
    flags = re.I if insensitive else 0
    cards = CardView.create(ctx,
                            status='visible',
                            title_regex=final_pattern,
                            regex_flags=flags)
    if count:
        print(sum(1 for _ in cards))
        return
    if use_json:
        print(cards.json())
    else:
        ctx.display.banner()
        ctx.display.show_cards(cards, sort=by, table_fields=fields)
Пример #7
0
 def create(context, **kwargs):
     '''Create a new CardView with the given filters on the cards to find.
     '''
     # Establish all base filters for cards nested resource query parameters.
     query_params = {}
     regex_flags = kwargs.get('regex_flags', 0)
     # Card status, in nested card resource
     status = kwargs.get('status', 'visible')
     valid_filters = ['all', 'closed', 'open', 'visible']
     if status not in valid_filters:
         click.secho(
             f'Card filter {status} is not valid! Use one of {",".join(valid_filters)}'
         )
         raise GTDException(1)
     query_params['cards'] = status
     query_params['card_fields'] = 'all'
     target_cards = []
     if (list_regex := kwargs.get('list_regex', None)) is not None:  # noqa
         # Are lists passed? If so, query to find out the list IDs corresponding to the names we have
         target_list_ids = []
         lists_json = context.connection.main_lists()
         pattern = re.compile(list_regex, flags=regex_flags)
         for list_object in lists_json:
             if pattern.search(list_object['name']):
                 target_list_ids.append(list_object['id'])
         # Iteratively pull IDs from each list, passing the common parameters to them
         for list_id in target_list_ids:
             cards_json = context.connection.trello.fetch_json(
                 f'/lists/{list_id}/cards', query_params=query_params)
             target_cards.extend(cards_json)
Пример #8
0
def cli(ctx, board, no_color, no_banner):
    '''gtd.py'''
    try:
        config = Configuration.from_file()
    except GTDException:
        click.echo('Could not find a valid config file for gtd.')
        if click.confirm('Would you like to create it interactively?'):
            ctx.invoke(onboard)
            click.secho('Re-run your command', fg='green')
            raise GTDException(0)
        else:
            click.secho(
                'Put your config file in one of the following locations:',
                fg='red')
            for l in Configuration.all_config_locations():
                print('  ' + l)
            raise
    if board is not None:
        config.board = board
    if no_color:
        config.color = False
    if no_banner:
        config.banner = False
    ctx.color = config.color
    ctx.obj = config
Пример #9
0
 def from_file(filename=None):
     if filename is None:
         filename = Configuration.find_config_file()
     with open(filename, 'r') as config_yaml:
         file_config = yaml.safe_load(config_yaml)
     for prop in [
             'api_key', 'api_secret', 'oauth_token', 'oauth_token_secret'
     ]:
         if file_config.get(prop, None) is not None:
             # great!
             continue
         else:
             print(
                 'A required property {0} in your configuration was not found!'
                 .format(prop))
             print('Check the file {0}'.format(filename))
             raise GTDException(1)
     # Hardcoded defaults are in the final 3 file_config.get() calls
     return Configuration(file_config['api_key'],
                          file_config['api_secret'],
                          file_config['oauth_token'],
                          file_config['oauth_token_secret'],
                          board=file_config.get('board', None),
                          color=file_config.get('color', True),
                          banner=file_config.get('banner', True))
Пример #10
0
 def find_config_file():
     # where to try finding the file in order
     for possible_loc in Configuration.all_config_locations():
         if os.path.isfile(possible_loc):
             return possible_loc
     # If we've gotten this far and did not find the configuration file, it does not exist
     raise GTDException(1)
Пример #11
0
def grep(config, pattern, insensitive, count, regexp, by, fields, json):
    '''egrep through titles of cards on this board. This command attemps to replicate a couple of grep flags
    faithfully, so if you're a power-user of grep this command will feel familiar.
    One deviation from grep is the --json flag, which outputs all matching cards in full JSON format.
    '''
    if not (pattern or regexp):
        click.secho(
            'No pattern provided to grep: use either the argument or -e',
            fg='red')
        raise GTDException(1)
    # Merge together the different regex arguments
    final_pattern = '|'.join(regexp) if regexp else ''
    if pattern and final_pattern:
        final_pattern = final_pattern + '|' + pattern
    elif pattern:
        final_pattern = pattern
    flags = re.I if insensitive else 0
    connection, board = BoardTool.start(config)
    cards = BoardTool.filter_cards(board,
                                   title_regex=final_pattern,
                                   regex_flags=flags)
    if count:
        print(sum(1 for _ in cards))
        return
    display = Display(config.color)
    if config.banner and not json:
        display.banner()
    display.show_cards(cards, use_json=json, sort=by, table_fields=fields)
Пример #12
0
    def show_cards(self, cards, tsv=False, sort='activity', table_fields=[]):
        '''Display an iterable of cards all at once.
        Uses a pretty-printed table by default, but can also print tab-separated values (TSV).
        Supports the following cli commands:
            show cards
            grep

        :param list(trello.Card)|iterable(trello.Card) cards: cards to show
        :param bool tsv: display these cards using a tab-separated value format
        :param str sort: the field name to sort by (must be a valid field name in this table)
        :param list table_fields: display only these fields
        '''
        # TODO construct the table dynamically instead of filtering down an already-constructed table
        # TODO implement a custom sorting functions so the table can be sorted by multiple columns
        table = prettytable.PrettyTable()
        table.field_names = self.fields.keys()
        table.align = 'l'
        if tsv:
            table.set_style(prettytable.PLAIN_COLUMNS)
        else:
            table.hrules = prettytable.FRAME
        with click.progressbar(list(cards), label='Fetching cards', width=0) as pg:
            for card in pg:
                table.add_row([x(card) for x in self.fields.values()])
        try:
            table[0]
        except IndexError:
            click.secho('No cards match!', fg='red')
            raise GTDException(1)
        if table_fields:
            print(table.get_string(fields=table_fields, sortby=sort))
        else:
            print(self.resize_and_get_table(table, self.fields.keys(), sort))
Пример #13
0
 def from_file(filename=None):
     if filename is None:
         filename = Configuration.find_config_file()
     with open(filename, 'r') as config_yaml:
         file_config = yaml.safe_load(config_yaml)
     for prop in ['api_key', 'api_secret', 'oauth_token', 'oauth_token_secret']:
         if file_config.get(prop, None) is not None:
             # great!
             continue
         else:
             print(f'A required property {prop} in your configuration was not found!')
             print(f'Check the file {filename}')
             raise GTDException(1)
     return Configuration(
         file_config['api_key'],
         file_config['api_secret'],
         file_config['oauth_token'],
         file_config['oauth_token_secret'],
         # No default board: first board chosen
         board=file_config.get('board', None),
         # Only used in tests
         test_board=file_config.get('test_board', None),
         # Terminal color by default
         color=file_config.get('color', True),
         # Don't print banner by default
         banner=file_config.get('banner', False),
         # No default inbox_list: first list chosen
         inbox_list=file_config.get('inbox_list', None),
         # By default, don't prompt user to open attachments of a card in review interface
         prompt_for_open_attachments=file_config.get('prompt_for_open_attachments', False),
         # By default, prompt user to add tags to untagged cards in review interface
         prompt_for_untagged_cards=file_config.get('prompt_for_untagged_cards', True),
     )
Пример #14
0
def search_for_regex(card, title_regex, regex_flags):
    try:
        return re.search(title_regex, card['name'], regex_flags)
    except re.error as e:
        click.secho(
            f'Invalid regular expression "{title_regex}" passed: {str(e)}',
            fg='red')
        raise GTDException(1)
Пример #15
0
 def search_for_regex(card):
     try:
         return re.search(title_regex, card.name, regex_flags)
     except re.error as e:
         click.secho(
             'Invalid regular expression "{1}" passed: {0}'.format(
                 str(e), title_regex),
             fg='red')
         raise GTDException(1)
Пример #16
0
 def __connect(self, config):
     trello_client = trello.TrelloClient(
         api_key=config.api_key,
         api_secret=config.api_secret,
         token=config.oauth_token,
         token_secret=config.oauth_token_secret,
     )
     try:
         # A simple API call (data reused in self.main_board) to initiate connection & test our credentials etc
         self.boards = trello_client.fetch_json(
             '/members/me/boards/?filter=open')
         return trello_client
     except requests.exceptions.ConnectionError:
         print('[FATAL] Could not connect to the Trello API!')
         raise GTDException(1)
     except trello.exceptions.Unauthorized:
         print('[FATAL] Trello API credentials are invalid')
         raise GTDException(1)
Пример #17
0
def info(workflow, banner):
    '''Learn more about gtd.py'''
    if workflow:
        click.secho(WORKFLOW_TEXT, fg='yellow')
        raise GTDException(0)
    elif banner:
        print(get_banner())
    else:
        print('gtd.py version {c}{0}{r}'.format(__version__, c=Colors.green, r=Colors.reset))
        print('{c}https://github.com/delucks/gtd.py/{r}\nPRs welcome\n'.format(c=Colors.green, r=Colors.reset))
Пример #18
0
    def show_cards(self, cards, use_json=False, tsv=False, table_fields=[], field_blacklist=[]):
        '''Display an iterable of cards all at once.
        Uses a pretty-printed table by default, but can also print JSON and tab-separated values (TSV).
        Supports the following cli commands:
            show cards
            grep

        :param list(trello.Card)|iterable(trello.Card) cards: cards to show
        :param bool use_json: display all metadata of these cards in JSON format
        :param bool tsv: display these cards using a tab-separated value format
        :param list table_fields: display only these fields (overrides field_blacklist)
        :param list field_blacklist: display all except these fields
        '''
        if use_json:
            sanitized_cards = list(map(
                lambda d: d.pop('client') and d,
                [c.__dict__.copy() for c in cards]
            ))
            tostr = self._force_json(sanitized_cards)
            print(json.dumps(tostr, sort_keys=True, indent=2))
        else:
            # TODO implement a custom sorting functions so the table can be sorted by multiple columns
            fields = OrderedDict()
            # This is done repetitively to establish column order
            fields['name'] = lambda c: c.name
            fields['list'] = lambda c: c.get_list().name
            fields['tags'] = lambda c: '\n'.join([l.name for l in c.list_labels]) if c.list_labels else ''
            fields['desc'] = lambda c: c.desc
            fields['due'] = lambda c: c.due or ''
            fields['last activity'] = lambda c: getattr(c, 'dateLastActivity')
            fields['board'] = lambda c: c.board.name
            fields['id'] = lambda c: getattr(c, 'id')
            fields['url'] = lambda c: getattr(c, 'shortUrl')
            table = prettytable.PrettyTable()
            table.field_names = fields.keys()
            table.align = 'l'
            if tsv:
                table.set_style(prettytable.PLAIN_COLUMNS)
            else:
                table.hrules = prettytable.FRAME
            with click.progressbar(list(cards), label='Fetching cards', width=0) as pg:
                for card in pg:
                    table.add_row([x(card) for x in fields.values()])
            try:
                table[0]
            except IndexError:
                click.secho('No cards match!', fg='red')
                raise GTDException(1)
            if table_fields:
                print(self.resize_and_get_table(table, table_fields))
            elif field_blacklist:
                f = set(fields.keys()) - set(field_blacklist)
                print(self.resize_and_get_table(table, list(f)))
            else:
                print(self.resize_and_get_table(table, fields.keys()))
Пример #19
0
def info(ctx, workflow, banner):
    '''Learn more about gtd.py'''
    if workflow:
        click.secho(WORKFLOW_TEXT, fg='yellow')
        raise GTDException(0)
    elif banner:
        print(get_banner(use_color=ctx.config.color))
    else:
        on = Colors.green if ctx.config.color else ''
        off = Colors.reset if ctx.config.color else ''
        print(f'gtd.py version {on}{__version__}{off}')
        print(
            f'Visit {on}https://github.com/delucks/gtd.py/{off} for more information'
        )
Пример #20
0
 def suggest_config_location():
     '''Do some platform detection and suggest a place for the users' config file to go'''
     system = platform.system()
     if system == 'Windows':
         print(
             'gtd.py support for Windows is rudimentary to none. Try to put your config file in $HOME/.gtd.yaml and run the script again'
         )
         raise GTDException(0)
     elif system == 'Darwin':
         preferred_location = os.path.expanduser('~/Library/Application Support/gtd/gtd.yaml')
     elif system == 'Linux':
         preferred_location = os.path.expanduser('~/.config/gtd/gtd.yaml')
     else:
         preferred_location = os.path.expanduser('~/.gtd.yaml')
     return preferred_location
Пример #21
0
def add_card(config, title, message, edit):
    '''Add a new card. If no title is provided, $EDITOR will be opened so you can write one.'''
    connection, board = BoardTool.start(config)
    inbox = BoardTool.get_inbox_list(connection, config)
    if not title:
        title = click.edit(require_save=True, text='<Title here>')
        if title is None:  # No changes were made in $EDITOR
            click.secho('No title entered for the new card!', fg='red')
            raise GTDException(1)
        else:
            title = title.strip()
    returned = inbox.add_card(name=title, desc=message)
    if edit:
        display = Display(config.color)
        list_lookup = BoardTool.list_lookup(board)
        label_lookup = BoardTool.label_lookup(board)
        CardTool.smart_menu(returned, display.show_card, list_lookup, label_lookup, Colors.yellow)
    else:
        click.secho('Successfully added card {0}!'.format(returned), fg='green')
Пример #22
0
def show_boards(config, json, tsv, by, show_all):
    '''Show all boards your account can access'''
    connection, board = BoardTool.start(config)
    display = Display(config.color)
    if config.banner and not json:
        display.banner()
    if show_all:
        boards = connection.trello.fetch_json('/members/me/boards/?filter=all')
    else:
        boards = connection.boards
    if json:
        display.show_raw(boards, use_json=json)
        return
    # Set up a table to hold our boards
    board_columns = ['name', 'activity', 'members', 'permission', 'url']
    if by not in board_columns:
        click.secho('Field {} is not a valid field: {}'.format(
            by, ','.join(board_columns)),
                    fg='red')
        raise GTDException(1)
    table = prettytable.PrettyTable()
    table.field_names = board_columns
    table.align = 'l'
    if tsv:
        table.set_style(prettytable.PLAIN_COLUMNS)
    else:
        table.hrules = prettytable.FRAME
    for b in boards:
        table.add_row([
            b['name'],
            b['dateLastActivity'] or '',
            len(b['memberships']),
            b['prefs']['permissionLevel'],
            b['shortUrl'],
        ])
    try:
        table[0]
    except IndexError:
        click.secho('You have no boards!', fg='red')
    print(table.get_string(sortby=by))
Пример #23
0
def show_boards(ctx, use_json, tsv, by, show_all):
    '''Show all boards your account can access'''
    if show_all:
        boards = ctx.connection.trello.fetch_json(
            '/members/me/boards/?filter=all')
    else:
        boards = ctx.connection.boards
    if use_json:
        print(json.dumps(boards, sort_keys=True, indent=2))
        return
    else:
        ctx.display.banner()
    # Set up a table to hold our boards
    board_columns = ['name', 'activity', 'members', 'permission', 'url']
    if by not in board_columns:
        click.secho(
            f'Field {by} is not a valid field: {",".join(board_columns)}',
            fg='red')
        raise GTDException(1)
    table = prettytable.PrettyTable()
    table.field_names = board_columns
    table.align = 'l'
    if tsv:
        table.set_style(prettytable.PLAIN_COLUMNS)
    else:
        table.hrules = prettytable.FRAME
    for b in boards:
        table.add_row([
            b['name'],
            b['dateLastActivity'] or '',
            len(b['memberships']),
            b['prefs']['permissionLevel'],
            b['shortUrl'],
        ])
    try:
        table[0]
    except IndexError:
        click.secho('You have no boards!', fg='red')
    print(table.get_string(sortby=by))
Пример #24
0
    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'
                )
Пример #25
0
 def smart_menu(card, f_display, list_choices, label_choices, color=None):
     '''make assumptions about what you want to do with a card and ask the user if they want to'''
     on = color if color else ''
     off = Colors.reset if color else ''
     card.fetch()
     f_display(card)
     if card.get_attachments():
         if prompt_for_confirmation(
                 '{0}Open attachments?{1}'.format(on, off), False):
             with DevNullRedirect():
                 for url in [
                         a.url for a in card.get_attachments()
                         if a.url is not None
                 ]:
                     webbrowser.open(url)
     if re.search(VALID_URL_REGEX, card.name):
         if prompt_for_confirmation(
                 '{0}Link in title detected, want to attach it & rename?{1}'
                 .format(on, off), True):
             CardTool.title_to_link(card)
     if not card.list_labels:
         print('{0}No tags on this card yet, want to add some?{1}'.format(
             on, off))
         CardTool.add_labels(card, label_choices)
     commands = {
         'archive': 'mark this card as closed',
         'attach': 'add, delete, or open attachments',
         '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 list (m)',
         'next': 'move on to the next card (n)',
         'open': 'open all links on this card (o)',
         'print': 'display this card (p)',
         'rename': 'change title of this card',
         'tag': 'add or remove tags on this card (t)',
         'quit': 'exit program'
     }
     command_completer = WordCompleter(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']:
             break
         elif user_input in ['p', 'print']:
             card.fetch()
             f_display(card)
         elif user_input in ['o', 'open']:
             with DevNullRedirect():
                 for url in [
                         a.url for a in card.get_attachments()
                         if a.url is not None
                 ]:
                     webbrowser.open(url)
         elif user_input in ['desc', 'description']:
             if CardTool.change_description(card):
                 print('Description changed!')
         elif user_input == 'delete':
             card.delete()
             print('Card deleted')
             break
         elif user_input == 'attach':
             CardTool.manipulate_attachments(card)
         elif user_input == 'archive':
             card.set_closed(True)
             print('Card archived')
             break
         elif user_input in ['t', 'tag']:
             CardTool.add_labels(card, label_choices)
         elif user_input == 'rename':
             CardTool.rename(card)
         elif user_input == 'duedate':
             CardTool.set_due_date(card)
         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 in ['m', 'move']:
             if CardTool.move_to_list(card, list_choices):
                 break
         elif user_input == '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(
                 '{0}{1}{2} is not a command, type "{0}help{2}" to view available commands'
                 .format(on, user_input, off))
Пример #26
0
        post_processed_cards = []
        # Regular expression on trello.Card.name
        if (title_regex := kwargs.get('title_regex',
                                      None)) is not None:  # noqa
            filters.append(
                partial(search_for_regex,
                        title_regex=title_regex,
                        regex_flags=regex_flags))
        # boolean queries about whether the card has things
        if (has_attachments := kwargs.get('has_attachments',
                                          None)) is not None:  # noqa
            filters.append(lambda c: c['badges']['attachments'] > 0)
        if (no_tags := kwargs.get('no_tags', None)) is not None:  # noqa
            filters.append(lambda c: not c['idLabels'])
        if (has_due_date := kwargs.get('has_due_date',
                                       None)) is not None:  # noqa
            filters.append(lambda c: c['due'])
        # comma-separated string of tags to filter on
        if (tags := kwargs.get('tags', None)) is not None:  # noqa
            filters.append(partial(check_for_label_presence, tags=tags))

        for card in target_cards:
            if all(filter_func(card) for filter_func in filters):
                post_processed_cards.append(card)

        if not post_processed_cards:
            click.secho('No cards matched the filters provided', fg='red')
            raise GTDException(0)
        # Create a CardView with those objects as the base
        return CardView(context=context, cards=post_processed_cards)