Ejemplo n.º 1
0
    def _sync_children(self):
        from wunderlist.models.hashtag import Hashtag
        from wunderlist.models.preferences import Preferences
        from wunderlist.models.reminder import Reminder

        start = time.time()
        user_revised = User.sync()
        log.info('Synced user in %s', time.time() - start)
        start = time.time()

        lists_revised = List.sync()
        log.info('Synced lists and tasks in %s', time.time() - start)
        start = time.time()

        # Changes to reminders or settings increment the User revision
        if user_revised:
            Preferences.sync()
            log.info('Synced preferences in %s', time.time() - start)
            start = time.time()

            Reminder.sync()
            log.info('Synced reminders in %s', time.time() - start)
            start = time.time()

        # Changes in lists or tasks require hashtags to be updated
        if lists_revised:
            Hashtag.sync()
            log.info('Synced hashtags in %s', time.time() - start)
Ejemplo n.º 2
0
def background_sync_if_necessary(seconds=30):
    last_sync = Preferences.current_prefs().last_sync

    # Avoid syncing on every keystroke, background_sync will also prevent
    # multiple concurrent syncs
    if last_sync is None or (datetime.now() - last_sync).total_seconds() > seconds:
        background_sync()
Ejemplo n.º 3
0
def commit(args, modifier=None):
    from wunderlist.api import tasks
    from wunderlist.sync import background_sync

    task = _task(args)
    prefs = Preferences.current_prefs()

    prefs.last_list_id = task.list_id

    task_info = tasks.create_task(task.list_id, task.title,
                                  assignee_id=task.assignee_id,
                                  recurrence_type=task.recurrence_type,
                                  recurrence_count=task.recurrence_count,
                                  due_date=task.due_date,
                                  reminder_date=task.reminder_date,
                                  starred=task.starred,
                                  completed=task.completed,
                                  note=task.note)

    # Output must be a UTF-8 encoded string
    print ('The task was added to ' + task.list_title).encode('utf-8')

    if modifier == 'alt':
        import webbrowser

        webbrowser.open('wunderlist://tasks/%d' % task_info['id'])

    background_sync()
    def reminder_date_combine(cls, date_component, time_component=None):
        """
        Returns a datetime based on the date portion of the date_component and
        the time portion of the time_component with special handling for an
        unspecified time_component. Based on the user preferences, a None
        time_component will result in either the default reminder time or an
        adjustment based on the current time if the reminder date is today.
        """
        prefs = Preferences.current_prefs()

        if isinstance(date_component, datetime):
            date_component = date_component.date()

        # Set a dynamic reminder date if due today
        if date_component == date.today() and time_component is None and prefs.reminder_today_offset:
            adjusted_now = datetime.now()
            adjusted_now -= timedelta(seconds=adjusted_now.second, microseconds=adjusted_now.microsecond)
            time_component = adjusted_now + prefs.reminder_today_offset_timedelta

            # "Round" to nearest 5 minutes, e.g. from :01 to :05, :44 to :45,
            # :50 to :50
            time_component += timedelta(minutes=(5 - time_component.minute % 5) % 5)

        # Default an unspecified time component on any day other than today to
        # the default reminder time
        if time_component is None:
            time_component = prefs.reminder_time

        if isinstance(time_component, datetime):
            time_component = time_component.time()

        return datetime.combine(date_component, time_component)
Ejemplo n.º 5
0
def parsedatetime_constants():
    from parsedatetime import Constants
    from wunderlist.models.preferences import Preferences

    loc = Preferences.current_prefs().date_locale or user_locale()

    return Constants(loc)
Ejemplo n.º 6
0
def parsedatetime_constants():
    from parsedatetime import Constants
    from wunderlist.models.preferences import Preferences

    loc = Preferences.current_prefs().date_locale or user_locale()

    return Constants(loc)
Ejemplo n.º 7
0
def icon_theme():
    global _icon_theme
    if not _icon_theme:
        prefs = Preferences.current_prefs()

        if prefs.icon_theme:
            _icon_theme = prefs.icon_theme
        else:
            _icon_theme = 'light' if alfred_is_dark() else 'dark'

    return _icon_theme
Ejemplo n.º 8
0
def icon_theme():
	global _icon_theme
	if not _icon_theme:
		prefs = Preferences.current_prefs()

		if prefs.icon_theme:
			_icon_theme = prefs.icon_theme
		else:
			_icon_theme = 'light' if alfred_is_dark() else 'dark'

	return _icon_theme
Ejemplo n.º 9
0
def commit(args, modifier=None):
    relaunch_command = None
    prefs = Preferences.current_prefs()
    action = args[1]

    if action == 'duration':
        relaunch_command = 'wl-upcoming '
        prefs.upcoming_duration = int(args[2])

    if relaunch_command:
        relaunch_alfred(relaunch_command)
def commit(args, modifier=None):
    relaunch_command = None
    prefs = Preferences.current_prefs()
    action = args[1]

    if action == 'duration':
        relaunch_command = 'wl-upcoming '
        prefs.upcoming_duration = int(args[2])

    if relaunch_command:
        relaunch_alfred(relaunch_command)
Ejemplo n.º 11
0
def commit(args, modifier=None):
    action = args[1]
    prefs = Preferences.current_prefs()
    relaunch_command = None

    if action == 'sort' and len(args) > 2:
        command = args[2]

        if command == 'toggle-skipped':
            prefs.hoist_skipped_tasks = not prefs.hoist_skipped_tasks
            relaunch_command = 'wl-due sort'
        else:
            try:
                index = int(command)
                order_info = _due_orders[index - 1]
                prefs.due_order = order_info['due_order']
                relaunch_command = 'wl-due '
            except IndexError:
                pass
            except ValueError:
                pass

    if relaunch_command:
        relaunch_alfred(relaunch_command)
Ejemplo n.º 12
0
def commit(args, modifier=None):
    action = args[1]
    prefs = Preferences.current_prefs()
    relaunch_command = None

    if action == 'sort' and len(args) > 2:
        command = args[2]

        if command == 'toggle-skipped':
            prefs.hoist_skipped_tasks = not prefs.hoist_skipped_tasks
            relaunch_command = 'wl-due sort'
        else:
            try:
                index = int(command)
                order_info = _due_orders[index - 1]
                prefs.due_order = order_info['due_order']
                relaunch_command = 'wl-due '
            except IndexError:
                pass
            except ValueError:
                pass

    if relaunch_command:
        relaunch_alfred(relaunch_command)
def filter(args):
    wf = workflow()
    prefs = Preferences.current_prefs()
    command = args[1] if len(args) > 1 else None
    duration_info = _duration_info(prefs.upcoming_duration)

    if command == 'duration':
        selected_duration = prefs.upcoming_duration

        # Apply selected duration option
        if len(args) > 2:
            try:
                selected_duration = int(args[2])
            except:
                pass

        duration_info = _duration_info(selected_duration)

        if 'custom' in duration_info:
            wf.add_item(duration_info['label'],
                        duration_info['subtitle'],
                        arg='-upcoming duration %d' % (duration_info['days']),
                        valid=True,
                        icon=icons.RADIO_SELECTED if duration_info['days']
                        == selected_duration else icons.RADIO)

        for duration_info in _durations:
            wf.add_item(duration_info['label'],
                        duration_info['subtitle'],
                        arg='-upcoming duration %d' % (duration_info['days']),
                        valid=True,
                        icon=icons.RADIO_SELECTED if duration_info['days']
                        == selected_duration else icons.RADIO)

        wf.add_item('Back', autocomplete='-upcoming ', icon=icons.BACK)

        return

    # Force a sync if not done recently or join if already running
    if not prefs.last_sync or \
       datetime.now() - prefs.last_sync > timedelta(seconds=30) or \
       is_running('sync'):
        sync()

    wf.add_item(duration_info['label'],
                subtitle='Change the duration for upcoming tasks',
                autocomplete='-upcoming duration ',
                icon=icons.UPCOMING)

    conditions = True

    # Build task title query based on the args
    for arg in args[1:]:
        if len(arg) > 1:
            conditions = conditions & (Task.title.contains(arg)
                                       | List.title.contains(arg))

    if conditions is None:
        conditions = True

    tasks = Task.select().join(List).where(
        Task.completed_at.is_null() &
        (Task.due_date < date.today() + timedelta(days=duration_info['days'] + 1)) &
        (Task.due_date > date.today() + timedelta(days=1)) &
        Task.list.is_null(False) &
        conditions
    )\
        .join(Reminder, JOIN.LEFT_OUTER, on=Reminder.task == Task.id)\
        .order_by(Task.due_date.asc(), Reminder.date.asc(), Task.order.asc())

    try:
        for t in tasks:
            wf.add_item(
                u'%s – %s' % (t.list_title, t.title),
                t.subtitle(),
                autocomplete='-task %s ' % t.id,
                icon=icons.TASK_COMPLETED if t.completed else icons.TASK)
    except OperationalError:
        background_sync()

    wf.add_item('Main menu', autocomplete='', icon=icons.BACK)

    # Make sure tasks stay up-to-date
    background_sync_if_necessary(seconds=2)
Ejemplo n.º 14
0
def filter(args):
    wf = workflow()
    prefs = Preferences.current_prefs()
    command = args[1] if len(args) > 1 else None

    # Show sort options
    if command == 'sort':
        for i, order_info in enumerate(_due_orders):
            wf.add_item(order_info['title'],
                        order_info['subtitle'],
                        arg='-due sort %d' % (i + 1),
                        valid=True,
                        icon=icons.RADIO_SELECTED if order_info['due_order']
                        == prefs.due_order else icons.RADIO)

        wf.add_item(
            'Highlight skipped recurring tasks',
            'Hoists recurring tasks that have been missed multiple times over to the top',
            arg='-due sort toggle-skipped',
            valid=True,
            icon=icons.CHECKBOX_SELECTED
            if prefs.hoist_skipped_tasks else icons.CHECKBOX)

        wf.add_item('Back', autocomplete='-due ', icon=icons.BACK)

        return

    # Force a sync if not done recently or wait on the current sync
    if not prefs.last_sync or \
       datetime.now() - prefs.last_sync > timedelta(seconds=30) or \
       is_running('sync'):
        sync()

    conditions = True

    # Build task title query based on the args
    for arg in args[1:]:
        if len(arg) > 1:
            conditions = conditions & (Task.title.contains(arg)
                                       | List.title.contains(arg))

    if conditions is None:
        conditions = True

    tasks = Task.select().join(
        List).where(Task.completed_at.is_null()
                    & (Task.due_date < date.today() + timedelta(days=1))
                    & Task.list.is_null(False) & conditions)

    # Sort the tasks according to user preference
    for key in prefs.due_order:
        order = 'asc'
        field = None
        if key[0] == '-':
            order = 'desc'
            key = key[1:]

        if key == 'due_date':
            field = Task.due_date
        elif key == 'list.order':
            field = List.order
        elif key == 'order':
            field = Task.order

        if field:
            if order == 'asc':
                tasks = tasks.order_by(field.asc())
            else:
                tasks = tasks.order_by(field.desc())

    try:
        if prefs.hoist_skipped_tasks:
            tasks = sorted(tasks, key=lambda t: -t.overdue_times)

        for t in tasks:
            wf.add_item(
                u'%s – %s' % (t.list_title, t.title),
                t.subtitle(),
                autocomplete='-task %s ' % t.id,
                icon=icons.TASK_COMPLETED if t.completed else icons.TASK)
    except OperationalError:
        background_sync()

    wf.add_item(u'Sort order',
                'Change the display order of due tasks',
                autocomplete='-due sort',
                icon=icons.SORT)

    wf.add_item('Main menu', autocomplete='', icon=icons.BACK)

    # Make sure tasks stay up-to-date
    background_sync_if_necessary(seconds=2)
Ejemplo n.º 15
0
def update_prerelease_channel():
	from wunderlist.models.preferences import Preferences

	prefs = Preferences.current_prefs()

	_update_settings['prerelease'] = prefs.prerelease_channel
Ejemplo n.º 16
0
def filter(args):
    query = ' '.join(args[1:])
    wf = workflow()
    prefs = Preferences.current_prefs()
    matching_hashtags = []

    if not query:
        wf.add_item('Begin typing to search tasks', '', icon=icons.SEARCH)

    hashtag_match = re.search(_hashtag_prompt_pattern, query)
    if hashtag_match:
        from wunderlist.models.hashtag import Hashtag

        hashtag_prompt = hashtag_match.group().lower()
        hashtags = Hashtag.select().where(
            Hashtag.id.contains(hashtag_prompt)).order_by(
                fn.Lower(Hashtag.tag).asc())

        for hashtag in hashtags:
            # If there is an exact match, do not show hashtags
            if hashtag.id == hashtag_prompt:
                matching_hashtags = []
                break

            matching_hashtags.append(hashtag)

    # Show hashtag prompt if there is more than one matching hashtag or the
    # hashtag being typed does not exactly match the single matching hashtag
    if len(matching_hashtags) > 0:
        for hashtag in matching_hashtags:
            wf.add_item(hashtag.tag[1:],
                        '',
                        autocomplete=u'-search %s%s ' %
                        (query[:hashtag_match.start()], hashtag.tag),
                        icon=icons.HASHTAG)

    else:
        conditions = True
        lists = workflow().stored_data('lists')
        matching_lists = None
        query = ' '.join(args[1:]).strip()
        list_query = None

        # Show all lists on the main search screen
        if not query:
            matching_lists = lists
        # Filter lists when colon is used
        if ':' in query:
            matching_lists = lists
            components = re.split(r':\s*', query, 1)
            list_query = components[0]
            if list_query:
                matching_lists = workflow().filter(
                    list_query,
                    lists if lists else [],
                    lambda l: l['title'],
                    # Ignore MATCH_ALLCHARS which is expensive and inaccurate
                    match_on=MATCH_ALL ^ MATCH_ALLCHARS)

                # If no matching list search against all tasks
                if matching_lists:
                    query = components[1] if len(components) > 1 else ''

                # If there is a list exactly matching the query ignore
                # anything else. This takes care of lists that are substrings
                # of other lists
                if len(matching_lists) > 1:
                    for l in matching_lists:
                        if l['title'].lower() == list_query.lower():
                            matching_lists = [l]
                            break

        if matching_lists:
            if not list_query:
                wf.add_item('Browse by hashtag',
                            autocomplete='-search #',
                            icon=icons.HASHTAG)

            if len(matching_lists) > 1:
                for l in matching_lists:
                    icon = icons.INBOX if l[
                        'list_type'] == 'inbox' else icons.LIST
                    wf.add_item(l['title'],
                                autocomplete='-search %s: ' % l['title'],
                                icon=icon)
            else:
                conditions = conditions & (Task.list
                                           == matching_lists[0]['id'])

        if not matching_lists or len(matching_lists) <= 1:
            for arg in query.split(' '):
                if len(arg) > 1:
                    conditions = conditions & (Task.title.contains(arg)
                                               | List.title.contains(arg))

            if conditions:
                if not prefs.show_completed_tasks:
                    conditions = Task.completed_at.is_null() & conditions

                tasks = Task.select().where(
                    Task.list.is_null(False) & conditions)

                # Default Wunderlist sort order reversed to show newest first
                tasks = tasks.join(List).order_by(Task.order.desc(),
                                                  List.order.asc())

                # Avoid excessive results
                tasks = tasks.limit(50)

                try:
                    for t in tasks:
                        wf.add_item(u'%s – %s' % (t.list_title, t.title),
                                    t.subtitle(),
                                    autocomplete='-task %s  ' % t.id,
                                    icon=icons.TASK_COMPLETED
                                    if t.completed else icons.TASK)
                except OperationalError:
                    background_sync()

            if prefs.show_completed_tasks:
                wf.add_item('Hide completed tasks',
                            arg='-pref show_completed_tasks --alfred %s' %
                            ' '.join(args),
                            valid=True,
                            icon=icons.HIDDEN)
            else:
                wf.add_item('Show completed tasks',
                            arg='-pref show_completed_tasks --alfred %s' %
                            ' '.join(args),
                            valid=True,
                            icon=icons.VISIBLE)

        wf.add_item('New search', autocomplete='-search ', icon=icons.CANCEL)
        wf.add_item('Main menu', autocomplete='', icon=icons.BACK)

        # Make sure tasks are up-to-date while searching
        background_sync()
def filter(args):
	prefs = Preferences.current_prefs()

	if 'reminder' in args:
		reminder_time = _parse_time(' '.join(args))

		if reminder_time is not None:
			workflow().add_item(
				'Change default reminder time',
				u'⏰ %s' % format_time(reminder_time, 'short'),
				arg=' '.join(args), valid=True, icon=icons.REMINDER
			)
		else:
			workflow().add_item(
				'Type a new reminder time',
				'Date offsets like the morning before the due date are not supported yet',
				valid=False, icon=icons.REMINDER
			)

		workflow().add_item(
			'Cancel',
			autocomplete=':pref', icon=icons.BACK
		)
	elif 'reminder_today' in args:
		reminder_today_offset = _parse_time(' '.join(args))

		if reminder_today_offset is not None:
			workflow().add_item(
				'Set a custom reminder offset',
				u'⏰ now + %s' % _format_time_offset(reminder_today_offset),
				arg=' '.join(args), valid=True, icon=icons.REMINDER
			)
		else:
			workflow().add_item(
				'Type a custom reminder offset',
				'Use the formats hh:mm or 2h 5m',
				valid=False, icon=icons.REMINDER
			)

		workflow().add_item(
			'30 minutes',
			arg=':pref reminder_today 30m', valid=True, icon=icons.REMINDER
		)

		workflow().add_item(
			'1 hour',
			'(default)',
			arg=':pref reminder_today 1h', valid=True, icon=icons.REMINDER
		)

		workflow().add_item(
			'90 minutes',
			arg=':pref reminder_today 90m', valid=True, icon=icons.REMINDER
		)

		workflow().add_item(
			'Always use the default reminder time',
			'Avoids adjusting the reminder based on the current date',
			arg=':pref reminder_today disabled', valid=True, icon=icons.CANCEL
		)

		workflow().add_item(
			'Cancel',
			autocomplete=':pref', icon=icons.BACK
		)
	else:
		current_user = User.get()

		if current_user and current_user.name:
			workflow().add_item(
				'Sign out',
				'You are logged in as ' + current_user.name,
				autocomplete=':logout', icon=icons.CANCEL
			)

		workflow().add_item(
			'Default reminder time',
			u'⏰ %s    Reminders without a specific time will be set to this time' % format_time(prefs.reminder_time, 'short'),
			autocomplete=':pref reminder ', icon=icons.REMINDER
		)

		workflow().add_item(
			'Default reminder when due today',
			u'⏰ %s    Default reminder time for tasks due today is %s' % (_format_time_offset(prefs.reminder_today_offset), 'relative to the current time' if prefs.reminder_today_offset else 'always %s' % format_time(prefs.reminder_time, 'short')),
			autocomplete=':pref reminder_today ', icon=icons.REMINDER
		)

		workflow().add_item(
			'Automatically set a reminder on the due date',
			u'Sets a default reminder for tasks with a due date.',
			arg=':pref automatic_reminders', valid=True, icon=icons.TASK_COMPLETED if prefs.automatic_reminders else icons.TASK
		)

		workflow().add_item(
			'Require explicit due keyword',
			'Requires the due keyword to avoid accidental due date extraction',
			arg=':pref explicit_keywords', valid=True, icon=icons.TASK_COMPLETED if prefs.explicit_keywords else icons.TASK
		)

		workflow().add_item(
			'Check for experimental updates to this workflow',
			'The workflow automatically checks for updates; enable this to include pre-releases',
			arg=':pref prerelease_channel', valid=True, icon=icons.TASK_COMPLETED if prefs.prerelease_channel else icons.TASK
		)

		workflow().add_item(
			'Force sync',
			'The workflow syncs automatically, but feel free to be forcible.',
			arg=':pref sync', valid=True, icon=icons.SYNC
		)

		workflow().add_item(
			'Switch theme',
			'Toggle between light and dark icons',
			arg=':pref retheme',
			valid=True,
			icon=icons.PAINTBRUSH
		)

		workflow().add_item(
			'Main menu',
			autocomplete='', icon=icons.BACK
		)
def commit(args):
	prefs = Preferences.current_prefs()
	relaunch_alfred = False

	if 'sync' in args:
		from wunderlist.sync import sync
		sync()
	elif 'explicit_keywords' in args:
		relaunch_alfred = True

		prefs.explicit_keywords = not prefs.explicit_keywords

		if prefs.explicit_keywords:
			print 'Remember to use the "due" keyword'
		else:
			print 'Implicit due dates enabled (e.g. "Recycling tomorrow")'
	elif 'reminder' in args:
		relaunch_alfred = True
		reminder_time = _parse_time(' '.join(args))

		if reminder_time is not None:
			prefs.reminder_time = reminder_time

			print 'Reminders will now default to %s' % format_time(reminder_time, 'short')
	elif 'reminder_today' in args:
		relaunch_alfred = True
		reminder_today_offset = None

		if not ('disabled' in args):
			reminder_today_offset = _parse_time(' '.join(args))

		prefs.reminder_today_offset = reminder_today_offset

		print 'The offset for current-day reminders is now %s' % _format_time_offset(reminder_today_offset)
	elif 'automatic_reminders' in args:
		relaunch_alfred = True

		prefs.automatic_reminders = not prefs.automatic_reminders

		if prefs.automatic_reminders:
			print 'A reminder will automatically be set for due tasks'
		else:
			print 'A reminder will not be added automatically'
	elif 'retheme' in args:
		relaunch_alfred = True
		prefs.icon_theme = 'light' if icons.icon_theme() == 'dark' else 'dark'

		print 'The workflow is now using the %s icon theme' % (prefs.icon_theme)
	elif 'prerelease_channel' in args:
		relaunch_alfred = True

		prefs.prerelease_channel = not prefs.prerelease_channel

		# Update the workflow settings and reverify the update data
		workflow().check_update(True)

		if prefs.prerelease_channel:
			print 'The workflow will prompt you to update to experimental pre-releases'
		else:
			print 'The workflow will only prompt you to update to final releases'
	if relaunch_alfred:
		import subprocess
		subprocess.call(['/usr/bin/env', 'osascript', 'bin/launch_alfred.scpt', 'wl:pref'])
Ejemplo n.º 19
0
def filter(args):
    prefs = Preferences.current_prefs()

    if 'reminder' in args:
        reminder_time = _parse_time(' '.join(args))

        if reminder_time is not None:
            workflow().add_item(
                'Change default reminder time',
                u'⏰ %s' % format_time(reminder_time, 'short'),
                arg=' '.join(args), valid=True, icon=icons.REMINDER
            )
        else:
            workflow().add_item(
                'Type a new reminder time',
                'Date offsets like the morning before the due date are not supported yet',
                valid=False, icon=icons.REMINDER
            )

        workflow().add_item(
            'Cancel',
            autocomplete='-pref', icon=icons.BACK
        )
    elif 'reminder_today' in args:
        reminder_today_offset = _parse_time(' '.join(args))

        if reminder_today_offset is not None:
            workflow().add_item(
                'Set a custom reminder offset',
                u'⏰ now + %s' % _format_time_offset(reminder_today_offset),
                arg=' '.join(args), valid=True, icon=icons.REMINDER
            )
        else:
            workflow().add_item(
                'Type a custom reminder offset',
                'Use the formats hh:mm or 2h 5m',
                valid=False, icon=icons.REMINDER
            )

        workflow().add_item(
            '30 minutes',
            arg='-pref reminder_today 30m', valid=True, icon=icons.REMINDER
        )

        workflow().add_item(
            '1 hour',
            '(default)',
            arg='-pref reminder_today 1h', valid=True, icon=icons.REMINDER
        )

        workflow().add_item(
            '90 minutes',
            arg='-pref reminder_today 90m', valid=True, icon=icons.REMINDER
        )

        workflow().add_item(
            'Always use the default reminder time',
            'Avoids adjusting the reminder based on the current date',
            arg='-pref reminder_today disabled', valid=True, icon=icons.CANCEL
        )

        workflow().add_item(
            'Cancel',
            autocomplete='-pref', icon=icons.BACK
        )
    elif 'default_list' in args:
        lists = workflow().stored_data('lists')
        matching_lists = lists

        if len(args) > 2:
            list_query = ' '.join(args[2:])
            if list_query:
                matching_lists = workflow().filter(
                    list_query,
                    lists,
                    lambda l: l['title'],
                    # Ignore MATCH_ALLCHARS which is expensive and inaccurate
                    match_on=MATCH_ALL ^ MATCH_ALLCHARS
                )

        for i, l in enumerate(matching_lists):
            if i == 1:
                workflow().add_item(
                    'Most recently used list',
                    'Default to the last list to which a task was added',
                    arg='-pref default_list %d' % DEFAULT_LIST_MOST_RECENT,
                    valid=True, icon=icons.RECURRENCE
                )
            icon = icons.INBOX if l['list_type'] == 'inbox' else icons.LIST
            workflow().add_item(
                l['title'],
                arg='-pref default_list %s' % l['id'],
                valid=True, icon=icon
            )

        workflow().add_item(
            'Cancel',
            autocomplete='-pref', icon=icons.BACK
        )
    else:
        current_user = None
        lists = workflow().stored_data('lists')
        loc = user_locale()
        default_list_name = 'Inbox'

        try:
            current_user = User.get()
        except User.DoesNotExist:
            pass
        except OperationalError:
            from wunderlist.sync import background_sync
            background_sync()

        if prefs.default_list_id == DEFAULT_LIST_MOST_RECENT:
            default_list_name = 'Most recent list'
        else:
            default_list_id = prefs.default_list_id
            default_list_name = next((l['title'] for l in lists if l['id'] == default_list_id), 'Inbox')

        if current_user and current_user.name:
            workflow().add_item(
                'Sign out',
                'You are logged in as ' + current_user.name,
                autocomplete='-logout', icon=icons.CANCEL
            )

        workflow().add_item(
            'Show completed tasks',
            'Includes completed tasks in search results',
            arg='-pref show_completed_tasks', valid=True, icon=icons.TASK_COMPLETED if prefs.show_completed_tasks else icons.TASK
        )

        workflow().add_item(
            'Default reminder time',
            u'⏰ %s    Reminders without a specific time will be set to this time' % format_time(prefs.reminder_time, 'short'),
            autocomplete='-pref reminder ', icon=icons.REMINDER
        )

        workflow().add_item(
            'Default reminder when due today',
            u'⏰ %s    Default reminder time for tasks due today is %s' % (_format_time_offset(prefs.reminder_today_offset), 'relative to the current time' if prefs.reminder_today_offset else 'always %s' % format_time(prefs.reminder_time, 'short')),
            autocomplete='-pref reminder_today ', icon=icons.REMINDER
        )

        workflow().add_item(
            'Default list',
            u'%s    Change the default list when creating new tasks' % default_list_name,
            autocomplete='-pref default_list ', icon=icons.LIST
        )

        workflow().add_item(
            'Automatically set a reminder on the due date',
            u'Sets a default reminder for tasks with a due date.',
            arg='-pref automatic_reminders', valid=True, icon=icons.TASK_COMPLETED if prefs.automatic_reminders else icons.TASK
        )

        if loc != 'en_US' or prefs.date_locale:
            workflow().add_item(
                'Force US English for dates',
                'Rather than the current locale (%s)' % loc,
                arg='-pref force_en_US', valid=True, icon=icons.TASK_COMPLETED if prefs.date_locale == 'en_US' else icons.TASK
            )

        workflow().add_item(
            'Require explicit due keyword',
            'Requires the due keyword to avoid accidental due date extraction',
            arg='-pref explicit_keywords', valid=True, icon=icons.TASK_COMPLETED if prefs.explicit_keywords else icons.TASK
        )

        workflow().add_item(
            'Check for experimental updates to this workflow',
            'The workflow automatically checks for updates; enable this to include pre-releases',
            arg=':pref prerelease_channel', valid=True, icon=icons.TASK_COMPLETED if prefs.prerelease_channel else icons.TASK
        )

        workflow().add_item(
            'Force sync',
            'The workflow syncs automatically, but feel free to be forcible.',
            arg='-pref sync', valid=True, icon=icons.SYNC
        )

        workflow().add_item(
            'Switch theme',
            'Toggle between light and dark icons',
            arg='-pref retheme',
            valid=True,
            icon=icons.PAINTBRUSH
        )

        workflow().add_item(
            'Main menu',
            autocomplete='', icon=icons.BACK
        )
Ejemplo n.º 20
0
def filter(args):
	task = _task(args)
	subtitle = []
	wf = workflow()

	if task.starred:
		subtitle.append(_star)

	if task.due_date:
		today = date.today()
		if task.due_date == today:
			date_format = 'Today'
		elif task.due_date.year == today.year:
			date_format = '%a, %b %d'
		else:
			date_format = '%b %d, %Y'

		subtitle.append('Due %s' % (task.due_date.strftime(date_format)))

	if task.recurrence_type:
		if task.recurrence_count > 1:
			subtitle.append('%s Every %d %ss' % (_recurrence, task.recurrence_count, task.recurrence_type))
		# Cannot simply add -ly suffix
		elif task.recurrence_type == 'day':
			subtitle.append('%s Daily' % (_recurrence))
		else:
			subtitle.append('%s %sly' % (_recurrence, task.recurrence_type.title()))

	if task.reminder_date:
		today = date.today()
		if task.reminder_date.date() == today:
			date_format = 'Today'
		elif task.reminder_date.date() == task.due_date:
			date_format = 'On due date'
		elif task.reminder_date.year == today.year:
			date_format = '%a, %b %d'
		else:
			date_format = '%b %d, %Y'

		subtitle.append('%s %s at %s' % (
			_reminder,
			task.reminder_date.strftime(date_format),
			format_time(task.reminder_date.time(), 'short'))
		)

	subtitle.append(task.title or 'Begin typing to add a new task')

	if task.has_list_prompt:
		lists = wf.stored_data('lists')
		if lists:
			for list in lists:
				# Show some full list names and some concatenated in command
				# suggestions
				sample_command = list['title']
				if random() > 0.5:
					sample_command = sample_command[:int(len(sample_command) * .75)]
				icon = icons.INBOX if list['list_type'] == 'inbox' else icons.LIST
				wf.add_item(list['title'], 'Assign task to this list, e.g. %s: %s' % (sample_command.lower(), task.title), autocomplete=' ' + task.phrase_with(list_title=list['title']), icon=icon)
			wf.add_item('Remove list', 'Tasks without a list are added to the Inbox', autocomplete=' ' + task.phrase_with(list_title=False), icon=icons.CANCEL)
		elif is_running('sync'):
			wf.add_item('Your lists are being synchronized', 'Please try again in a few moments', autocomplete=' ' + task.phrase_with(list_title=False), icon=icons.BACK)
	
	# Task has an unfinished recurrence phrase
	elif task.has_recurrence_prompt:
		wf.add_item('Every month', 'Same day every month, e.g. every mo', uid="recurrence_1m", autocomplete=' %s ' % task.phrase_with(recurrence='every month'), icon=icons.RECURRENCE)
		wf.add_item('Every week', 'Same day every week, e.g. every week, every Tuesday', uid="recurrence_1w", autocomplete=' %s ' % task.phrase_with(recurrence='every week'), icon=icons.RECURRENCE)
		wf.add_item('Every year', 'Same date every year, e.g. every 1 y, every April 15', uid="recurrence_1y", autocomplete=' %s ' % task.phrase_with(recurrence='every year'), icon=icons.RECURRENCE)
		wf.add_item('Every 3 months', 'Same day every 3 months, e.g. every 3 months', uid="recurrence_3m", autocomplete=' %s ' % task.phrase_with(recurrence='every 3 months'), icon=icons.RECURRENCE)
		wf.add_item('Remove recurrence', autocomplete=' ' + task.phrase_with(recurrence=False), icon=icons.CANCEL)

	# Task has an unfinished due date phrase
	elif task.has_due_date_prompt:
		wf.add_item('Today', 'e.g. due today', autocomplete=' %s ' % task.phrase_with(due_date='due today'), icon=icons.TODAY)
		wf.add_item('Tomorrow', 'e.g. due tomorrow', autocomplete=' %s ' % task.phrase_with(due_date='due tomorrow'), icon=icons.TOMORROW)
		wf.add_item('Next Week', 'e.g. due next week', autocomplete=' %s ' % task.phrase_with(due_date='due next week'), icon=icons.NEXT_WEEK)
		wf.add_item('Next Month', 'e.g. due next month', autocomplete=' %s ' % task.phrase_with(due_date='due next month'), icon=icons.CALENDAR)
		wf.add_item('Next Year', 'e.g. due next year, due April 15', autocomplete=' %s ' % task.phrase_with(due_date='due next year'), icon=icons.CALENDAR)
		wf.add_item('Remove due date', 'Add "not due" to fix accidental dates, or see wl:pref', autocomplete=' ' + task.phrase_with(due_date=False), icon=icons.CANCEL)

	# Task has an unfinished reminder phrase
	elif task.has_reminder_prompt:
		prefs = Preferences.current_prefs()
		default_reminder_time = format_time(prefs.reminder_time, 'short')
		due_date_hint = ' on the due date' if task.due_date else ''
		wf.add_item('Reminder at %s%s' % (default_reminder_time, due_date_hint), 'e.g. r %s' % default_reminder_time, autocomplete=' %s ' % task.phrase_with(reminder_date='remind me at %s' % format_time(prefs.reminder_time, 'short')), icon=icons.REMINDER)
		wf.add_item('At noon%s' % due_date_hint, 'e.g. reminder noon', autocomplete=' %s ' % task.phrase_with(reminder_date='remind me at noon'), icon=icons.REMINDER)
		wf.add_item('At 8:00 PM%s' % due_date_hint, 'e.g. remind at 8:00 PM', autocomplete=' %s ' % task.phrase_with(reminder_date='remind me at 8:00pm'), icon=icons.REMINDER)
		wf.add_item('At dinner%s' % due_date_hint, 'e.g. alarm at dinner', autocomplete=' %s ' % task.phrase_with(reminder_date='remind me at dinner'), icon=icons.REMINDER)
		wf.add_item('Today at 6:00 PM', 'e.g. remind me today at 6pm', autocomplete=' %s ' % task.phrase_with(reminder_date='remind me today at 6:00pm'), icon=icons.REMINDER)
		wf.add_item('Remove reminder', autocomplete=' ' + task.phrase_with(reminder_date=False), icon=icons.CANCEL)

	# Main menu for tasks
	else:
		wf.add_item(task.list_title + u' – create a new task...', '   '.join(subtitle), arg='--stored-query', valid=task.title != '', icon=icons.TASK)

		title = 'Change list' if task.list_title else 'Select a list'
		wf.add_item(title, 'Prefix the task, e.g. Automotive: ' + task.title, autocomplete=' ' + task.phrase_with(list_title=True), icon=icons.LIST)

		title = 'Change the due date' if task.due_date else 'Set a due date'
		wf.add_item(title, '"due" followed by any date-related phrase, e.g. due next Tuesday; due May 4', autocomplete=' ' + task.phrase_with(due_date=True), icon=icons.CALENDAR)

		title = 'Change the recurrence' if task.recurrence_type else 'Make it a recurring task'
		wf.add_item(title, '"every" followed by a unit of time, e.g. every 2 months; every year; every 4w', autocomplete=' ' + task.phrase_with(recurrence=True), icon=icons.RECURRENCE)

		title = 'Change the reminder' if task.reminder_date else 'Set a reminder'
		wf.add_item(title, '"remind me" followed by a time and/or date, e.g. remind me at noon; r 10am; alarm 8:45p', autocomplete=' ' + task.phrase_with(reminder_date=True), icon=icons.REMINDER)

		if task.starred:
			wf.add_item('Remove star', 'Remove * from the task', autocomplete=' ' + task.phrase_with(starred=False), icon=icons.STAR_REMOVE)
		else:
			wf.add_item('Star', 'End the task with * (asterisk)', autocomplete=' ' + task.phrase_with(starred=True), icon=icons.STAR)

		wf.add_item('Main menu', autocomplete='', icon=icons.BACK)
Ejemplo n.º 21
0
def sync(background=False):
    from wunderlist.models import base, root, list, task, user, hashtag, reminder
    from peewee import OperationalError

    # If a sync is already running, wait for it to finish. Otherwise, store
    # the current pid in alfred-workflow's pid cache file
    if not background:
        if is_running('sync'):
            wait_count = 0
            while is_running('sync'):
                time.sleep(.25)
                wait_count += 1

                if wait_count == 2:
                    notify('Please wait...', 'The workflow is making sure your tasks are up-to-date')

            return False

        pidfile = workflow().cachefile('sync.pid')

        with open(pidfile, 'wb') as file_obj:
            file_obj.write('{0}'.format(os.getpid()))

    Preferences.current_prefs().last_sync = datetime.now()

    base.BaseModel._meta.database.create_tables([
        root.Root,
        list.List,
        task.Task,
        user.User,
        hashtag.Hashtag,
        reminder.Reminder
    ], safe=True)

    # Perform a query that requires the latest schema; if it fails due to a
    # mismatched scheme, delete the old database and re-sync
    try:
        task.Task.select().where(task.Task.recurrence_count > 0).count()
        hashtag.Hashtag.select().where(hashtag.Hashtag.tag == '').count()
    except OperationalError:
        base.BaseModel._meta.database.close()
        workflow().clear_data(lambda f: 'wunderlist.db' in f)

        # Make sure that this sync does not try to wait until its own process
        # finishes
        sync(background=True)
        return

    first_sync = False

    try:
        root.Root.get()
    except root.Root.DoesNotExist:
        first_sync = True

    root.Root.sync(background=background)

    if background:
        if first_sync:
            notify('Initial sync has completed', 'All of your tasks are now available for browsing')

        # If executed manually, this will pass on to the post notification action
        print 'Sync completed successfully'

    return True
Ejemplo n.º 22
0
def filter(args):
    wf = workflow()
    prefs = Preferences.current_prefs()
    command = args[1] if len(args) > 1 else None

    # Show sort options
    if command == 'sort':
        for i, order_info in enumerate(_due_orders):
            wf.add_item(order_info['title'], order_info['subtitle'], arg='-due sort %d' % (i + 1), valid=True, icon=icons.RADIO_SELECTED if order_info['due_order'] == prefs.due_order else icons.RADIO)

        wf.add_item('Highlight skipped recurring tasks', 'Hoists recurring tasks that have been missed multiple times over to the top', arg='-due sort toggle-skipped', valid=True, icon=icons.CHECKBOX_SELECTED if prefs.hoist_skipped_tasks else icons.CHECKBOX)

        wf.add_item('Back', autocomplete='-due ', icon=icons.BACK)

        return

    # Force a sync if not done recently or wait on the current sync
    if not prefs.last_sync or \
       datetime.now() - prefs.last_sync > timedelta(seconds=30) or \
       is_running('sync'):
        sync()

    conditions = True

    # Build task title query based on the args
    for arg in args[1:]:
        if len(arg) > 1:
            conditions = conditions & (Task.title.contains(arg) | List.title.contains(arg))

    if conditions is None:
        conditions = True

    tasks = Task.select().join(List).where(
        Task.completed_at.is_null() &
        (Task.due_date < date.today() + timedelta(days=1)) &
        Task.list.is_null(False) &
        conditions
    )

    # Sort the tasks according to user preference
    for key in prefs.due_order:
        order = 'asc'
        field = None
        if key[0] == '-':
            order = 'desc'
            key = key[1:]

        if key == 'due_date':
            field = Task.due_date
        elif key == 'list.order':
            field = List.order
        elif key == 'order':
            field = Task.order

        if field:
            if order == 'asc':
                tasks = tasks.order_by(field.asc())
            else:
                tasks = tasks.order_by(field.desc())

    try:
        if prefs.hoist_skipped_tasks:
            tasks = sorted(tasks, key=lambda t: -t.overdue_times)

        for t in tasks:
            wf.add_item(u'%s – %s' % (t.list_title, t.title), t.subtitle(), autocomplete='-task %s ' % t.id, icon=icons.TASK_COMPLETED if t.completed else icons.TASK)
    except OperationalError:
        background_sync()

    wf.add_item(u'Sort order', 'Change the display order of due tasks', autocomplete='-due sort', icon=icons.SORT)

    wf.add_item('Main menu', autocomplete='', icon=icons.BACK)

    # Make sure tasks stay up-to-date
    background_sync_if_necessary(seconds=2)
Ejemplo n.º 23
0
def filter(args):
    wf = workflow()
    prefs = Preferences.current_prefs()
    command = args[1] if len(args) > 1 else None
    duration_info = _duration_info(prefs.upcoming_duration)

    if command == 'duration':
        selected_duration = prefs.upcoming_duration

        # Apply selected duration option
        if len(args) > 2:
            try:
                selected_duration = int(args[2])
            except:
                pass

        duration_info = _duration_info(selected_duration)

        if 'custom' in duration_info:
            wf.add_item(duration_info['label'], duration_info['subtitle'], arg='-upcoming duration %d' % (duration_info['days']), valid=True, icon=icons.RADIO_SELECTED if duration_info['days'] == selected_duration else icons.RADIO)

        for duration_info in _durations:
            wf.add_item(duration_info['label'], duration_info['subtitle'], arg='-upcoming duration %d' % (duration_info['days']), valid=True, icon=icons.RADIO_SELECTED if duration_info['days'] == selected_duration else icons.RADIO)

        wf.add_item('Back', autocomplete='-upcoming ', icon=icons.BACK)

        return

    # Force a sync if not done recently or join if already running
    if not prefs.last_sync or \
       datetime.now() - prefs.last_sync > timedelta(seconds=30) or \
       is_running('sync'):
        sync()

    wf.add_item(duration_info['label'], subtitle='Change the duration for upcoming tasks', autocomplete='-upcoming duration ', icon=icons.UPCOMING)

    conditions = True

    # Build task title query based on the args
    for arg in args[1:]:
        if len(arg) > 1:
            conditions = conditions & (Task.title.contains(arg) | List.title.contains(arg))

    if conditions is None:
        conditions = True

    tasks = Task.select().join(List).where(
        Task.completed_at.is_null() &
        (Task.due_date < date.today() + timedelta(days=duration_info['days'] + 1)) &
        (Task.due_date > date.today() + timedelta(days=1)) &
        Task.list.is_null(False) &
        conditions
    )\
        .join(Reminder, JOIN.LEFT_OUTER, on=Reminder.task == Task.id)\
        .order_by(Task.due_date.asc(), Reminder.date.asc(), Task.order.asc())

    try:
        for t in tasks:
            wf.add_item(u'%s – %s' % (t.list_title, t.title), t.subtitle(), autocomplete='-task %s ' % t.id, icon=icons.TASK_COMPLETED if t.completed else icons.TASK)
    except OperationalError:
        background_sync()

    wf.add_item('Main menu', autocomplete='', icon=icons.BACK)

    # Make sure tasks stay up-to-date
    background_sync_if_necessary(seconds=2)
Ejemplo n.º 24
0
def commit(args, modifier=None):
    prefs = Preferences.current_prefs()
    relaunch_command = '-pref'

    if '--alfred' in args:
        relaunch_command = ' '.join(args[args.index('--alfred') + 1:])

    if 'sync' in args:
        from wunderlist.sync import sync
        sync('background' in args)

        relaunch_command = None
    elif 'show_completed_tasks' in args:
        prefs.show_completed_tasks = not prefs.show_completed_tasks

        if prefs.show_completed_tasks:
            print 'Completed tasks are now visible in the workflow'
        else:
            print 'Completed tasks will not be visible in the workflow'
    elif 'default_list' in args:
        default_list_id = None
        lists = workflow().stored_data('lists')

        if len(args) > 2:
            default_list_id = int(args[2])

        prefs.default_list_id = default_list_id

        if default_list_id:
            default_list_name = next(
                (l['title'] for l in lists if l['id'] == default_list_id),
                'Inbox')
            print 'Tasks will be added to your %s list by default' % default_list_name
        else:
            print 'Tasks will be added to the Inbox by default'
    elif 'explicit_keywords' in args:
        prefs.explicit_keywords = not prefs.explicit_keywords

        if prefs.explicit_keywords:
            print 'Remember to use the "due" keyword'
        else:
            print 'Implicit due dates enabled (e.g. "Recycling tomorrow")'
    elif 'reminder' in args:
        reminder_time = _parse_time(' '.join(args))

        if reminder_time is not None:
            prefs.reminder_time = reminder_time

            print 'Reminders will now default to %s' % format_time(
                reminder_time, 'short')
    elif 'reminder_today' in args:
        reminder_today_offset = None

        if not 'disabled' in args:
            reminder_today_offset = _parse_time(' '.join(args))

        prefs.reminder_today_offset = reminder_today_offset

        print 'The offset for current-day reminders is now %s' % _format_time_offset(
            reminder_today_offset)
    elif 'automatic_reminders' in args:
        prefs.automatic_reminders = not prefs.automatic_reminders

        if prefs.automatic_reminders:
            print 'A reminder will automatically be set for due tasks'
        else:
            print 'A reminder will not be added automatically'
    elif 'retheme' in args:
        prefs.icon_theme = 'light' if icons.icon_theme() == 'dark' else 'dark'

        print 'The workflow is now using the %s icon theme' % (
            prefs.icon_theme)
    elif 'prerelease_channel' in args:

        prefs.prerelease_channel = not prefs.prerelease_channel

        # Update the workflow settings and reverify the update data
        workflow().check_update(True)

        if prefs.prerelease_channel:
            print 'The workflow will prompt you to update to experimental pre-releases'
        else:
            print 'The workflow will only prompt you to update to final releases'

    if relaunch_command:
        relaunch_alfred(relaunch_command)
Ejemplo n.º 25
0
def filter(args):
    task = _task(args)
    subtitle = task_subtitle(task)
    wf = workflow()
    matching_hashtags = []

    if not task.title:
        subtitle = 'Begin typing to add a new task'

    # Preload matching hashtags into a list so that we can get the length
    if task.has_hashtag_prompt:
        from wunderlist.models.hashtag import Hashtag

        hashtags = Hashtag.select().where(Hashtag.id.contains(task.hashtag_prompt.lower())).order_by(fn.Lower(Hashtag.tag).asc())

        for hashtag in hashtags:
            matching_hashtags.append(hashtag)

    # Show hashtag prompt if there is more than one matching hashtag or the
    # hashtag being typed does not exactly match the single matching hashtag
    if task.has_hashtag_prompt and len(matching_hashtags) > 0 and (len(matching_hashtags) > 1 or task.hashtag_prompt != matching_hashtags[0].tag):
        for hashtag in matching_hashtags:
            wf.add_item(hashtag.tag[1:], '', autocomplete=' ' + task.phrase_with(hashtag=hashtag.tag) + ' ', icon=icons.HASHTAG)

    elif task.has_list_prompt:
        lists = wf.stored_data('lists')
        if lists:
            for list in lists:
                # Show some full list names and some concatenated in command
                # suggestions
                sample_command = list['title']
                if random() > 0.5:
                    sample_command = sample_command[:int(len(sample_command) * .75)]
                icon = icons.INBOX if list['list_type'] == 'inbox' else icons.LIST
                wf.add_item(list['title'], 'Assign task to this list, e.g. %s: %s' % (sample_command.lower(), task.title), autocomplete=' ' + task.phrase_with(list_title=list['title']), icon=icon)
            wf.add_item('Remove list', 'Tasks without a list are added to the Inbox', autocomplete=' ' + task.phrase_with(list_title=False), icon=icons.CANCEL)
        elif is_running('sync'):
            wf.add_item('Your lists are being synchronized', 'Please try again in a few moments', autocomplete=' ' + task.phrase_with(list_title=False), icon=icons.BACK)

    # Task has an unfinished recurrence phrase
    elif task.has_recurrence_prompt:
        wf.add_item('Every month', 'Same day every month, e.g. every mo', uid="recurrence_1m", autocomplete=' %s ' % task.phrase_with(recurrence='every month'), icon=icons.RECURRENCE)
        wf.add_item('Every week', 'Same day every week, e.g. every week, every Tuesday', uid="recurrence_1w", autocomplete=' %s ' % task.phrase_with(recurrence='every week'), icon=icons.RECURRENCE)
        wf.add_item('Every year', 'Same date every year, e.g. every 1 y, every April 15', uid="recurrence_1y", autocomplete=' %s ' % task.phrase_with(recurrence='every year'), icon=icons.RECURRENCE)
        wf.add_item('Every 3 months', 'Same day every 3 months, e.g. every 3 months', uid="recurrence_3m", autocomplete=' %s ' % task.phrase_with(recurrence='every 3 months'), icon=icons.RECURRENCE)
        wf.add_item('Remove recurrence', autocomplete=' ' + task.phrase_with(recurrence=False), icon=icons.CANCEL)

    # Task has an unfinished due date phrase
    elif task.has_due_date_prompt:
        wf.add_item('Today', 'e.g. due today', autocomplete=' %s ' % task.phrase_with(due_date='due today'), icon=icons.TODAY)
        wf.add_item('Tomorrow', 'e.g. due tomorrow', autocomplete=' %s ' % task.phrase_with(due_date='due tomorrow'), icon=icons.TOMORROW)
        wf.add_item('Next Week', 'e.g. due next week', autocomplete=' %s ' % task.phrase_with(due_date='due next week'), icon=icons.NEXT_WEEK)
        wf.add_item('Next Month', 'e.g. due next month', autocomplete=' %s ' % task.phrase_with(due_date='due next month'), icon=icons.CALENDAR)
        wf.add_item('Next Year', 'e.g. due next year, due April 15', autocomplete=' %s ' % task.phrase_with(due_date='due next year'), icon=icons.CALENDAR)
        wf.add_item('Remove due date', 'Add "not due" to fix accidental dates, or see wl-pref', autocomplete=' ' + task.phrase_with(due_date=False), icon=icons.CANCEL)

    # Task has an unfinished reminder phrase
    elif task.has_reminder_prompt:
        prefs = Preferences.current_prefs()
        default_reminder_time = format_time(prefs.reminder_time, 'short')
        due_date_hint = ' on the due date' if task.due_date else ''
        wf.add_item('Reminder at %s%s' % (default_reminder_time, due_date_hint), 'e.g. r %s' % default_reminder_time, autocomplete=' %s ' % task.phrase_with(reminder_date='remind me at %s' % format_time(prefs.reminder_time, 'short')), icon=icons.REMINDER)
        wf.add_item('At noon%s' % due_date_hint, 'e.g. reminder noon', autocomplete=' %s ' % task.phrase_with(reminder_date='remind me at noon'), icon=icons.REMINDER)
        wf.add_item('At 8:00 PM%s' % due_date_hint, 'e.g. remind at 8:00 PM', autocomplete=' %s ' % task.phrase_with(reminder_date='remind me at 8:00pm'), icon=icons.REMINDER)
        wf.add_item('At dinner%s' % due_date_hint, 'e.g. alarm at dinner', autocomplete=' %s ' % task.phrase_with(reminder_date='remind me at dinner'), icon=icons.REMINDER)
        wf.add_item('Today at 6:00 PM', 'e.g. remind me today at 6pm', autocomplete=' %s ' % task.phrase_with(reminder_date='remind me today at 6:00pm'), icon=icons.REMINDER)
        wf.add_item('Remove reminder', autocomplete=' ' + task.phrase_with(reminder_date=False), icon=icons.CANCEL)

    # Main menu for tasks
    else:
        wf.add_item(task.list_title + u' – create a new task...', subtitle, modifier_subtitles={
            'alt': u'…then edit it in the Wunderlist app    %s' % subtitle
        }, arg='--stored-query', valid=task.title != '', icon=icons.TASK)

        title = 'Change list' if task.list_title else 'Select a list'
        wf.add_item(title, 'Prefix the task, e.g. Automotive: ' + task.title, autocomplete=' ' + task.phrase_with(list_title=True), icon=icons.LIST)

        title = 'Change the due date' if task.due_date else 'Set a due date'
        wf.add_item(title, '"due" followed by any date-related phrase, e.g. due next Tuesday; due May 4', autocomplete=' ' + task.phrase_with(due_date=True), icon=icons.CALENDAR)

        title = 'Change the recurrence' if task.recurrence_type else 'Make it a recurring task'
        wf.add_item(title, '"every" followed by a unit of time, e.g. every 2 months; every year; every 4w', autocomplete=' ' + task.phrase_with(recurrence=True), icon=icons.RECURRENCE)

        title = 'Change the reminder' if task.reminder_date else 'Set a reminder'
        wf.add_item(title, '"remind me" followed by a time and/or date, e.g. remind me at noon; r 10am; alarm 8:45p', autocomplete=' ' + task.phrase_with(reminder_date=True), icon=icons.REMINDER)

        if task.starred:
            wf.add_item('Remove star', 'Remove * from the task', autocomplete=' ' + task.phrase_with(starred=False), icon=icons.STAR_REMOVE)
        else:
            wf.add_item('Star', 'End the task with * (asterisk)', autocomplete=' ' + task.phrase_with(starred=True), icon=icons.STAR)

        wf.add_item('Main menu', autocomplete='', icon=icons.BACK)
Ejemplo n.º 26
0
def filter(args):
    prefs = Preferences.current_prefs()

    if 'reminder' in args:
        reminder_time = _parse_time(' '.join(args))

        if reminder_time is not None:
            workflow().add_item('Change default reminder time',
                                u'⏰ %s' % format_time(reminder_time, 'short'),
                                arg=' '.join(args),
                                valid=True,
                                icon=icons.REMINDER)
        else:
            workflow().add_item(
                'Type a new reminder time',
                'Date offsets like the morning before the due date are not supported yet',
                valid=False,
                icon=icons.REMINDER)

        workflow().add_item('Cancel', autocomplete='-pref', icon=icons.BACK)
    elif 'reminder_today' in args:
        reminder_today_offset = _parse_time(' '.join(args))

        if reminder_today_offset is not None:
            workflow().add_item('Set a custom reminder offset',
                                u'⏰ now + %s' %
                                _format_time_offset(reminder_today_offset),
                                arg=' '.join(args),
                                valid=True,
                                icon=icons.REMINDER)
        else:
            workflow().add_item('Type a custom reminder offset',
                                'Use the formats hh:mm or 2h 5m',
                                valid=False,
                                icon=icons.REMINDER)

        workflow().add_item('30 minutes',
                            arg='-pref reminder_today 30m',
                            valid=True,
                            icon=icons.REMINDER)

        workflow().add_item('1 hour',
                            '(default)',
                            arg='-pref reminder_today 1h',
                            valid=True,
                            icon=icons.REMINDER)

        workflow().add_item('90 minutes',
                            arg='-pref reminder_today 90m',
                            valid=True,
                            icon=icons.REMINDER)

        workflow().add_item(
            'Always use the default reminder time',
            'Avoids adjusting the reminder based on the current date',
            arg='-pref reminder_today disabled',
            valid=True,
            icon=icons.CANCEL)

        workflow().add_item('Cancel', autocomplete='-pref', icon=icons.BACK)
    elif 'default_list' in args:
        lists = workflow().stored_data('lists')
        matching_lists = lists

        if len(args) > 2:
            list_query = ' '.join(args[2:])
            if list_query:
                matching_lists = workflow().filter(
                    list_query,
                    lists,
                    lambda l: l['title'],
                    # Ignore MATCH_ALLCHARS which is expensive and inaccurate
                    match_on=MATCH_ALL ^ MATCH_ALLCHARS)

        for i, l in enumerate(matching_lists):
            if i == 1:
                workflow().add_item(
                    'Most recently used list',
                    'Default to the last list to which a task was added',
                    arg='-pref default_list %d' % DEFAULT_LIST_MOST_RECENT,
                    valid=True,
                    icon=icons.RECURRENCE)
            icon = icons.INBOX if l['list_type'] == 'inbox' else icons.LIST
            workflow().add_item(l['title'],
                                arg='-pref default_list %s' % l['id'],
                                valid=True,
                                icon=icon)

        workflow().add_item('Cancel', autocomplete='-pref', icon=icons.BACK)
    else:
        current_user = None
        lists = workflow().stored_data('lists')
        default_list_name = 'Inbox'

        try:
            current_user = User.get()
        except User.DoesNotExist:
            pass
        except OperationalError:
            from wunderlist.sync import background_sync
            background_sync()

        if prefs.default_list_id == DEFAULT_LIST_MOST_RECENT:
            default_list_name = 'Most recent list'
        else:
            default_list_id = prefs.default_list_id
            default_list_name = next(
                (l['title'] for l in lists if l['id'] == default_list_id),
                'Inbox')

        if current_user and current_user.name:
            workflow().add_item('Sign out',
                                'You are logged in as ' + current_user.name,
                                autocomplete='-logout',
                                icon=icons.CANCEL)

        workflow().add_item('Show completed tasks',
                            'Includes completed tasks in search results',
                            arg='-pref show_completed_tasks',
                            valid=True,
                            icon=icons.TASK_COMPLETED
                            if prefs.show_completed_tasks else icons.TASK)

        workflow().add_item(
            'Default reminder time',
            u'⏰ %s    Reminders without a specific time will be set to this time'
            % format_time(prefs.reminder_time, 'short'),
            autocomplete='-pref reminder ',
            icon=icons.REMINDER)

        workflow().add_item(
            'Default reminder when due today',
            u'⏰ %s    Default reminder time for tasks due today is %s' %
            (_format_time_offset(prefs.reminder_today_offset),
             'relative to the current time' if prefs.reminder_today_offset else
             'always %s' % format_time(prefs.reminder_time, 'short')),
            autocomplete='-pref reminder_today ',
            icon=icons.REMINDER)

        workflow().add_item(
            'Default list',
            u'%s    Change the default list when creating new tasks' %
            default_list_name,
            autocomplete='-pref default_list ',
            icon=icons.LIST)

        workflow().add_item(
            'Automatically set a reminder on the due date',
            u'Sets a default reminder for tasks with a due date.',
            arg='-pref automatic_reminders',
            valid=True,
            icon=icons.TASK_COMPLETED
            if prefs.automatic_reminders else icons.TASK)

        workflow().add_item(
            'Require explicit due keyword',
            'Requires the due keyword to avoid accidental due date extraction',
            arg='-pref explicit_keywords',
            valid=True,
            icon=icons.TASK_COMPLETED
            if prefs.explicit_keywords else icons.TASK)

        workflow().add_item(
            'Check for experimental updates to this workflow',
            'The workflow automatically checks for updates; enable this to include pre-releases',
            arg=':pref prerelease_channel',
            valid=True,
            icon=icons.TASK_COMPLETED
            if prefs.prerelease_channel else icons.TASK)

        workflow().add_item(
            'Force sync',
            'The workflow syncs automatically, but feel free to be forcible.',
            arg='-pref sync',
            valid=True,
            icon=icons.SYNC)

        workflow().add_item('Switch theme',
                            'Toggle between light and dark icons',
                            arg='-pref retheme',
                            valid=True,
                            icon=icons.PAINTBRUSH)

        workflow().add_item('Main menu', autocomplete='', icon=icons.BACK)
    def _parse(self):
        cls = type(self)
        phrase = self.phrase
        cal = parsedatetime_calendar()
        wf = workflow()
        lists = wf.stored_data('lists')
        prefs = Preferences.current_prefs()
        ignore_due_date = False

        match = re.search(HASHTAG_PROMPT_PATTERN, phrase)
        if match:
            self.hashtag_prompt = match.group(1)
            self.has_hashtag_prompt = True

        match = re.search(SLASHES_PATTERN, phrase)
        if match:
            self._note_phrase = match.group(1) + match.group(2)
            self.note = re.sub(
                WHITESPACE_CLEANUP_PATTERN, ' ', match.group(2)).strip()
            phrase = phrase[:match.start()] + phrase[match.end():]

        match = re.search(STAR_PATTERN, phrase)
        if match:
            self.starred = True
            self._starred_phrase = match.group()
            phrase = phrase[:match.start()] + phrase[match.end():]

        match = re.search(NOT_DUE_PATTERN, phrase)
        if match:
            ignore_due_date = True
            phrase = phrase[:match.start()] + phrase[match.end():]

        match = re.search(LIST_TITLE_PATTERN, phrase)
        if lists and match:
            if match.group(1):
                matching_lists = wf.filter(
                    match.group(1),
                    lists,
                    lambda l: l['title'],
                    # Ignore MATCH_ALLCHARS which is expensive and inaccurate
                    match_on=MATCH_ALL ^ MATCH_ALLCHARS
                )

                # Take the first match as the desired list
                if matching_lists:
                    self.list_id = matching_lists[0]['id']
                    self.list_title = matching_lists[0]['title']
            # The list name was empty
            else:
                self.has_list_prompt = True

            if self.list_title or self.has_list_prompt:
                self._list_phrase = match.group()
                phrase = phrase[:match.start()] + phrase[match.end():]

        # Parse and remove the recurrence phrase first so that any dates do
        # not interfere with the due date
        match = re.search(RECURRENCE_PATTERN, phrase)
        if match:
            type_phrase = match.group(2) if match.group(2) else match.group(3)
            if type_phrase:
                # Look up the recurrence type based on the first letter of the
                # work or abbreviation used in the phrase
                self.recurrence_type = RECURRENCE_TYPES[type_phrase[0].lower()]
                self.recurrence_count = int(match.group(1) or 1)
            else:
                match = re.search(RECURRENCE_BY_DATE_PATTERN, phrase)
                if match:
                    recurrence_phrase = match.group()
                    dates = cal.nlp(match.group(1), version=2)

                    if dates:
                        # Only remove the first date following `every`
                        datetime_info = dates[0]
                        # Set due_date if a datetime was found and it is not time only
                        if datetime_info[1].hasDate:
                            self.due_date = datetime_info[0].date()
                            date_expression = datetime_info[4]

                            # FIXME: This logic could be improved to better
                            # differentiate between week and year expressions

                            # If the date expression is only one word and the next
                            # due date is less than one week from now, set a
                            # weekly recurrence, e.g. every Tuesday
                            if len(date_expression.split(' ')) == 1 and self.due_date < date.today() + timedelta(days=8):
                                self.recurrence_count = 1
                                self.recurrence_type = 'week'
                            # Otherwise expect a multi-word value like a date,
                            # e.g. every May 17
                            else:
                                self.recurrence_count = 1
                                self.recurrence_type = 'year'

                            self.has_recurrence_prompt = False

                            # Pull in any words between the `due` keyword and the
                            # actual date text
                            date_pattern = re.escape(date_expression)
                            date_pattern = r'.*?' + date_pattern

                            # Prepare to set the recurrence phrase below
                            match = re.search(date_pattern, recurrence_phrase)

            # This is just the "every" keyword with no date following
            if not self.recurrence_type:
                self.has_recurrence_prompt = True

            self._recurrence_phrase = match.group()
            phrase = phrase.replace(self._recurrence_phrase, '', 1)


        reminder_info = None
        match = re.search(REMINDER_PATTERN, phrase)
        if match:
            datetimes = cal.nlp(match.group(2), version=2)

            # If there is at least one date immediately following the reminder
            # phrase use it as the reminder date
            if datetimes and datetimes[0][2] == 0:
                # Only remove the first date following the keyword
                reminder_info = datetimes[0]

                self._reminder_phrase = match.group(1) + reminder_info[4]
                phrase = phrase.replace(self._reminder_phrase, '', 1)
            # Otherwise if there is just a reminder phrase, set the reminder
            # to the default time on the date due
            else:
                # There is no text following the reminder phrase, prompt for a reminder
                if not match.group(2):
                    self.has_reminder_prompt = True
                self._reminder_phrase = match.group(1)

                # Careful, this might just be the letter "r" so rather than
                # replacing it is better to strip out by index
                phrase = phrase[:match.start(1)] + phrase[match.end(1):]


        due_keyword = None
        potential_date_phrase = None
        if not ignore_due_date:
            match = re.search(DUE_PATTERN, phrase)
            # Search for the due date only following the `due` keyword
            if match:
                due_keyword = match.group(1)

                if match.group(2):
                    potential_date_phrase = match.group(2)
            # Otherwise find a due date anywhere in the phrase
            elif not prefs.explicit_keywords:
                potential_date_phrase = phrase

        if potential_date_phrase:
            dates = cal.nlp(potential_date_phrase, version=2)

            if dates:
                # Only remove the first date following `due`
                datetime_info = dates[0]
                # Set due_date if a datetime was found and it is not time only
                if datetime_info[1].hasDate:
                    self.due_date = datetime_info[0].date()
                elif datetime_info[1].hasTime and not self.due_date:
                    self.due_date = date.today()

                if self.due_date:
                    # Pull in any words between the `due` keyword and the
                    # actual date text
                    date_pattern = re.escape(datetime_info[4])

                    if due_keyword:
                        date_pattern = re.escape(due_keyword) + r'.*?' + date_pattern

                    due_date_phrase_match = re.search(date_pattern, phrase)

                    if due_date_phrase_match:
                        self._due_date_phrase = due_date_phrase_match.group()
                        phrase = phrase.replace(self._due_date_phrase, '', 1)

                    # If the due date specifies a time, set it as the reminder
                    if datetime_info[1].hasTime:
                        if datetime_info[1].hasDate:
                            self.reminder_date = datetime_info[0]
                        elif self.due_date:
                            self.reminder_date = datetime.combine(self.due_date, datetime_info[0].time())
                # Just a time component
                else:
                    due_keyword = None
            # No dates in the phrase
            else:
                due_keyword = None

        # The word due was not followed by a date
        if due_keyword and not self._due_date_phrase:
            self.has_due_date_prompt = True
            self._due_date_phrase = match.group(1)

            # Avoid accidentally replacing "due" inside words elsewhere in the
            # string
            phrase = phrase[:match.start(1)] + phrase[match.end(1):]

        if self.recurrence_type and not self.due_date:
            self.due_date = date.today()

        if self._reminder_phrase:
            # If a due date is set, a time-only reminder is relative to that
            # date; otherwise if there is no due date it is relative to today
            reference_date = self.due_date if self.due_date else date.today()

            if reminder_info:
                (dt, datetime_context, _, _, _) = reminder_info

                # Date and time; use as-is
                if datetime_context.hasTime and datetime_context.hasDate:
                    self.reminder_date = dt
                # Time only; set the reminder on the due day
                elif datetime_context.hasTime:
                    self.reminder_date = cls.reminder_date_combine(reference_date, dt)
                # Date only; set the default reminder time on that day
                elif datetime_context.hasDate:
                    self.reminder_date = cls.reminder_date_combine(dt)

            else:
                self.reminder_date = cls.reminder_date_combine(reference_date)

        # Look for a list title at the end of the remaining phrase, like
        # "in list Office"
        if not self.list_title:
            matches = re.finditer(INFIX_LIST_KEYWORD_PATTERN, phrase)
            for match in matches:
                subphrase = phrase[match.end():]

                # Just a couple characters are too likely to result in a false
                # positive, but allow it if the letters are capitalized
                if len(subphrase) > 2 or subphrase == subphrase.upper():
                    matching_lists = wf.filter(
                        subphrase,
                        lists,
                        lambda l: l['title'],
                        # Ignore MATCH_ALLCHARS which is expensive and inaccurate
                        match_on=MATCH_ALL ^ MATCH_ALLCHARS
                    )

                    # Take the first match as the desired list
                    if matching_lists:
                        self.list_id = matching_lists[0]['id']
                        self.list_title = matching_lists[0]['title']
                        self._list_phrase = match.group() + subphrase
                        phrase = phrase[:match.start()]
                        break

        # No list parsed, assign to inbox
        if not self.list_title:
            if prefs.default_list_id and lists:
                if prefs.default_list_id == DEFAULT_LIST_MOST_RECENT:
                    self.list_id = prefs.last_list_id
                else:
                    self.list_id = prefs.default_list_id
                default_list = next((l for l in lists if l['id'] == self.list_id), None)
                if default_list:
                    self.list_title = default_list['title']

            if not self.list_title:
                if lists:
                    inbox = lists[0]
                    self.list_id = inbox['id']
                    self.list_title = inbox['title']
                else:
                    self.list_id = 0
                    self.list_title = 'Inbox'

        # Set an automatic reminder when there is a due date without a
        # specified reminder
        if self.due_date and not self.reminder_date and prefs.automatic_reminders:
            self.reminder_date = cls.reminder_date_combine(self.due_date)

        # Condense extra whitespace remaining in the task title after parsing
        self.title = re.sub(WHITESPACE_CLEANUP_PATTERN, ' ', phrase).strip()
Ejemplo n.º 28
0
def commit(args, modifier=None):
    prefs = Preferences.current_prefs()
    relaunch_command = '-pref'

    if '--alfred' in args:
        relaunch_command = ' '.join(args[args.index('--alfred') + 1:])

    if 'sync' in args:
        from wunderlist.sync import sync
        sync('background' in args)

        relaunch_command = None
    elif 'show_completed_tasks' in args:
        prefs.show_completed_tasks = not prefs.show_completed_tasks

        if prefs.show_completed_tasks:
            print 'Completed tasks are now visible in the workflow'
        else:
            print 'Completed tasks will not be visible in the workflow'
    elif 'default_list' in args:
        default_list_id = None
        lists = workflow().stored_data('lists')

        if len(args) > 2:
            default_list_id = int(args[2])

        prefs.default_list_id = default_list_id

        if default_list_id:
            default_list_name = next((l['title'] for l in lists if l['id'] == default_list_id), 'Inbox')
            print 'Tasks will be added to your %s list by default' % default_list_name
        else:
            print 'Tasks will be added to the Inbox by default'
    elif 'explicit_keywords' in args:
        prefs.explicit_keywords = not prefs.explicit_keywords

        if prefs.explicit_keywords:
            print 'Remember to use the "due" keyword'
        else:
            print 'Implicit due dates enabled (e.g. "Recycling tomorrow")'
    elif 'reminder' in args:
        reminder_time = _parse_time(' '.join(args))

        if reminder_time is not None:
            prefs.reminder_time = reminder_time

            print 'Reminders will now default to %s' % format_time(reminder_time, 'short')
    elif 'reminder_today' in args:
        reminder_today_offset = None

        if not 'disabled' in args:
            reminder_today_offset = _parse_time(' '.join(args))

        prefs.reminder_today_offset = reminder_today_offset

        print 'The offset for current-day reminders is now %s' % _format_time_offset(reminder_today_offset)
    elif 'automatic_reminders' in args:
        prefs.automatic_reminders = not prefs.automatic_reminders

        if prefs.automatic_reminders:
            print 'A reminder will automatically be set for due tasks'
        else:
            print 'A reminder will not be added automatically'
    elif 'retheme' in args:
        prefs.icon_theme = 'light' if icons.icon_theme() == 'dark' else 'dark'

        print 'The workflow is now using the %s icon theme' % (prefs.icon_theme)
    elif 'prerelease_channel' in args:

        prefs.prerelease_channel = not prefs.prerelease_channel

        # Update the workflow settings and reverify the update data
        workflow().check_update(True)

        if prefs.prerelease_channel:
            print 'The workflow will prompt you to update to experimental pre-releases'
        else:
            print 'The workflow will only prompt you to update to final releases'
    elif 'force_en_US' in args:
        if prefs.date_locale:
            prefs.date_locale = None
            print 'The workflow will expect your local language and date format'
        else:
            prefs.date_locale = 'en_US'
            print 'The workflow will expect dates in US English'

    if relaunch_command:
        relaunch_alfred('wl%s' % relaunch_command)