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)
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()
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)
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)
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
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): 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)
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)
def update_prerelease_channel(): from wunderlist.models.preferences import Preferences prefs = Preferences.current_prefs() _update_settings['prerelease'] = prefs.prerelease_channel
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'])
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 )
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)
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
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)
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)
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)
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)
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()
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)