Skip to content

Commit

Permalink
Merge branch 'release/0.7.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
idpaterson committed Nov 1, 2017
2 parents 98d4a36 + 7f0fe16 commit 7f92ab3
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 22 deletions.
Binary file modified Wunderlist.alfredworkflow
Binary file not shown.
2 changes: 1 addition & 1 deletion lib/six
Submodule six updated from 06daa9 to 7c0b7f
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "alfred-wunderlist-workflow",
"version": "0.6.5",
"version": "0.7.0",
"dependencies": {},
"private": true,
"devDependencies": {
Expand Down
13 changes: 13 additions & 0 deletions src/wunderlist/api/notes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import wunderlist.api.base as api


def create_note(task_id, content):
params = {
'task_id': int(task_id),
'content': content
}

req = api.post('notes', params)
info = req.json()

return info
7 changes: 6 additions & 1 deletion src/wunderlist/api/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def task(id):

return info

def create_task(list_id, title, assignee_id=None, recurrence_type=None, recurrence_count=None, due_date=None, reminder_date=None, starred=False, completed=False):
def create_task(list_id, title, assignee_id=None, recurrence_type=None, recurrence_count=None, due_date=None, reminder_date=None, starred=False, completed=False, note=None):
params = {
'list_id': int(list_id),
'title': title,
Expand All @@ -86,6 +86,11 @@ def create_task(list_id, title, assignee_id=None, recurrence_type=None, recurren

reminders.create_reminder(info['id'], reminder_date)

if note:
from wunderlist.api import notes

notes.create_note(info['id'], note)

return info

def update_task(id, revision, title=NO_CHANGE, assignee_id=NO_CHANGE, recurrence_type=NO_CHANGE, recurrence_count=NO_CHANGE, due_date=NO_CHANGE, reminder_date=NO_CHANGE, starred=NO_CHANGE, completed=NO_CHANGE):
Expand Down
7 changes: 6 additions & 1 deletion src/wunderlist/handlers/new_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
_star = u'★'
_recurrence = u'↻'
_reminder = u'⏰'
_note = u'✏️'

def _task(args):
return TaskParser(' '.join(args))
Expand Down Expand Up @@ -49,6 +50,9 @@ def task_subtitle(task):

subtitle.append(task.title)

if task.note:
subtitle.append('%s %s' % (_note, task.note))

return ' '.join(subtitle)

def filter(args):
Expand Down Expand Up @@ -160,7 +164,8 @@ def commit(args, modifier=None):
due_date=task.due_date,
reminder_date=task.reminder_date,
starred=task.starred,
completed=task.completed)
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')
Expand Down
19 changes: 17 additions & 2 deletions src/wunderlist/handlers/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from wunderlist import icons
from wunderlist.models.preferences import Preferences, DEFAULT_LIST_MOST_RECENT
from wunderlist.models.user import User
from wunderlist.util import format_time, parsedatetime_calendar, relaunch_alfred, workflow
from wunderlist.util import format_time, parsedatetime_calendar, relaunch_alfred, user_locale, workflow


def _parse_time(phrase):
Expand Down Expand Up @@ -139,6 +139,7 @@ def filter(args):
else:
current_user = None
lists = workflow().stored_data('lists')
loc = user_locale()
default_list_name = 'Inbox'

try:
Expand Down Expand Up @@ -192,6 +193,13 @@ def filter(args):
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',
Expand Down Expand Up @@ -301,6 +309,13 @@ def commit(args, modifier=None):
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(relaunch_command)
relaunch_alfred('wl%s' % relaunch_command)
23 changes: 18 additions & 5 deletions src/wunderlist/models/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
DEFAULT_LIST_MOST_RECENT = -1

AUTOMATIC_REMINDERS_KEY = 'automatic_reminders'
DEFAULT_LIST_ID = 'default_list_id'
DEFAULT_LIST_ID_KEY = 'default_list_id'
DUE_ORDER_KEY = 'due_order'
EXPLICIT_KEYWORDS_KEY = 'explicit_keywords'
HOIST_SKIPPED_TASKS_KEY = 'hoist_skipped_tasks'
Expand All @@ -17,6 +17,7 @@
REMINDER_TODAY_OFFSET_KEY = 'reminder_today_offset'
SHOW_COMPLETED_TASKS_KEY = 'show_completed_tasks'
UPCOMING_DURATION_KEY = 'upcoming_duration'
DATE_LOCALE_KEY = 'date_locale'

class Preferences(object):

Expand Down Expand Up @@ -62,10 +63,14 @@ def __init__(self, data):
workflow().store_data('prefs', self._data)

def _set(self, key, value):
if self._data.get('key') != value:
if value is None and key in self._data:
del self._data[key]
elif self._data.get(key) != value:
self._data[key] = value
else:
return

workflow().store_data('prefs', self._data)
workflow().store_data('prefs', self._data)

def _get(self, key, default=None, type=str):
value = self._data.get(key)
Expand Down Expand Up @@ -179,8 +184,16 @@ def upcoming_duration(self, upcoming_duration):

@property
def default_list_id(self):
return self._get(DEFAULT_LIST_ID, None)
return self._get(DEFAULT_LIST_ID_KEY, None)

@default_list_id.setter
def default_list_id(self, default_list_id):
self._set(DEFAULT_LIST_ID, default_list_id)
self._set(DEFAULT_LIST_ID_KEY, default_list_id)

@property
def date_locale(self):
return self._get(DATE_LOCALE_KEY, None)

@date_locale.setter
def date_locale(self, date_locale):
self._set(DATE_LOCALE_KEY, date_locale)
16 changes: 16 additions & 0 deletions src/wunderlist/models/task_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
# Split words ignoring leading and trailing punctuation
WORD_SEPARATOR_PATTERN = re.compile(r'\W*\s+\W*', re.UNICODE)

# Anything following the '//' delimiter
SLASHES_PATTERN = re.compile(r'( //)(.*)$', re.DOTALL)

# Maps first letter to the API recurrence type
RECURRENCE_TYPES = {
'd': 'day',
Expand All @@ -66,6 +69,7 @@ class TaskParser(object):
assignee_id = None
starred = False
completed = False
note = None

has_list_prompt = False
has_due_date_prompt = False
Expand All @@ -78,6 +82,7 @@ class TaskParser(object):
_recurrence_phrase = None
_reminder_phrase = None
_starred_phrase = None
_note_phrase = None

def __init__(self, phrase):
self.phrase = phrase.strip()
Expand All @@ -97,6 +102,13 @@ def _parse(self):
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
Expand Down Expand Up @@ -478,6 +490,10 @@ def phrase_with(self, title=None, list_title=None, due_date=None, recurrence=Non
else:
pass

# Adds a note
if self.note:
components.append(self._note_phrase)

# Remove any empty string components, often a blank title
components = [component for component in components if component]

Expand Down
13 changes: 10 additions & 3 deletions src/wunderlist/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,16 @@ def parsedatetime_calendar():


def parsedatetime_constants():
import locale
from parsedatetime import Constants
from wunderlist.models.preferences import Preferences

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

return Constants(loc)


def user_locale():
import locale

loc = locale.getlocale(locale.LC_TIME)[0]

Expand All @@ -61,8 +69,7 @@ def parsedatetime_constants():
except IndexError:
loc = 'en_US'

return Constants(loc)

return loc

def format_time(time, format):
c = parsedatetime_constants()
Expand Down
11 changes: 4 additions & 7 deletions tasks/options/readme.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
New in version <%= pkg.version %>:
* #147 - Fixes 100% CPU usage background task on macOS 10.12.4
* Pull Request #150 - Task note, courtesy of @bogdal

More details:
https://github.com/idpaterson/alfred-wunderlist-workflow/releases/tag/<%= pkg.version %>


The 0.6 series includes the following major changes:
* Added upcoming and due screens
* Search and browse tasks
* New command format allowing partial commands like wls instead of wl-search
* Complete, delete, and view tasks in Wunderlist desktop app
The 0.7 series includes the following major changes:
* Add a note while creating a task with, e.g. task title // note text (thanks, @bogdal)

More details:
https://github.com/idpaterson/alfred-wunderlist-workflow/releases/tag/0.6.0
https://github.com/idpaterson/alfred-wunderlist-workflow/releases/tag/0.7.0
58 changes: 57 additions & 1 deletion tests/wunderlist/models/test_task_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def initials(phrase):
"""
return re.sub(r'(?:^| +)(\S)\S*', r'\1', phrase)

def assert_task(task, phrase=None, title=None, list_id=None, list_title=None, due_date=None, recurrence_type=None, recurrence_count=None, reminder_date=None, assignee_id=None, starred=False, completed=False, has_list_prompt=False, has_due_date_prompt=False, has_recurrence_prompt=False, has_reminder_prompt=False, has_hashtag_prompt=False):
def assert_task(task, phrase=None, title=None, list_id=None, list_title=None, due_date=None, recurrence_type=None, recurrence_count=None, reminder_date=None, assignee_id=None, starred=False, completed=False, has_list_prompt=False, has_due_date_prompt=False, has_recurrence_prompt=False, has_reminder_prompt=False, has_hashtag_prompt=False, note=None):
assert task.phrase == phrase
assert task.title == title

Expand All @@ -194,6 +194,7 @@ def assert_task(task, phrase=None, title=None, list_id=None, list_title=None, du
assert task.has_recurrence_prompt == has_recurrence_prompt
assert task.has_reminder_prompt == has_reminder_prompt
assert task.has_hashtag_prompt == has_hashtag_prompt
assert task.note == note

#
# Basics
Expand Down Expand Up @@ -989,6 +990,61 @@ def test_ignore_infix_asterisk(self):

assert_task(task, phrase=phrase, title=title, starred=False)

#
# Note
#

class TestNote():

note_delimiter = '//'

def test_note(self):
title = 'a sample task'
note = 'my note'
phrase = '%s %s%s' % (title, self.note_delimiter, note)
task = TaskParser(phrase)

assert_task(task, phrase=phrase, title=title, note=note)

def test_note_with_whitespace(self):
title = 'a sample task'
note = 'my note'
phrase = '%s %s %s' % (title, self.note_delimiter, note)
task = TaskParser(phrase)

assert_task(task, phrase=phrase, title=title, note=note)

def test_multiline_note(self):
title = 'a sample task'
source_note = 'my\ntest\tnote'
note = 'my test note'
phrase = '%s %s %s' % (title, self.note_delimiter, note)
task = TaskParser(phrase)

assert_task(task, phrase=phrase, title=title, note=note)

def test_note_with_star(self):
title = 'a sample task'
note = 'my note'
phrase = '%s* %s%s' % (title, self.note_delimiter, note)
task = TaskParser(phrase)

assert_task(task, phrase=phrase, title=title, starred=True, note=note)

def test_note_with_star_whitespace(self):
title = 'a sample task'
note = 'my note'
phrase = '%s * %s%s' % (title, self.note_delimiter, note)
task = TaskParser(phrase)

assert_task(task, phrase=phrase, title=title, starred=True, note=note)

def test_ignore_note_without_dedicated_delimiter(self):
phrase = 'read blog https://example.com/'
task = TaskParser(phrase)

assert_task(task, phrase=phrase, title=phrase, note=None)

#
# phrase_with()
#
Expand Down

0 comments on commit 7f92ab3

Please sign in to comment.