-
Notifications
You must be signed in to change notification settings - Fork 9
FIX: compatibility with Python v2.x #9
base: master
Are you sure you want to change the base?
Changes from 2 commits
deb9dc2
b9d9e7a
5f5af12
ec2f205
d87c529
71b6937
f84d5bf
ac5c86f
e46fc3a
dae5165
c878bf7
0bda3de
11080b6
983b3a2
36cadce
161b031
87d9eaa
9a16193
68d2b20
fe5d5f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,9 @@ | ||
#!/usr/bin/python | ||
# -*- coding: utf-8 -*- | ||
|
||
import os | ||
import re | ||
import io | ||
|
||
__version__ = '0.2.2' | ||
|
||
|
@@ -16,14 +20,20 @@ | |
'search' | ||
] | ||
|
||
todo_data_regex = re.compile('^(?:(x) )?(?:(\d{4}-\d{2}-\d{2}) )?(?:\(([A-Z])\) )?(?:(\d{4}-\d{2}-\d{2}) )?') | ||
todo_data_regex = re.compile( \ | ||
'^(?:(x) )?' + \ | ||
'(?:(\d{4}-\d{2}-\d{2}) )?' + \ | ||
'(?:\(([A-Z])\) )?' + \ | ||
'(?:(\d{4}-\d{2}-\d{2}) )?' \ | ||
) | ||
todo_project_regex = re.compile(' \+(\S*)') | ||
todo_context_regex = re.compile(' @(\S*)') | ||
todo_tag_regex = re.compile(' (\S*):(\S*)') | ||
|
||
|
||
def from_dicts(todos): | ||
"""Convert a list of todo dicts to a list of :class:`todotxtio.Todo` objects. | ||
""" | ||
Convert a list of todo dicts to a list of :class:`todotxtio.Todo` objects. | ||
|
||
:param list todos: A list of todo dicts | ||
:rtype: list | ||
|
@@ -32,7 +42,8 @@ def from_dicts(todos): | |
|
||
|
||
def from_stream(stream, close=True): | ||
"""Load a todo list from an already-opened stream. | ||
""" | ||
Load a todo list from an already-opened stream. | ||
|
||
:param file stream: A file-like object | ||
:param bool close: Whetever to close the stream or not after all operation are finised | ||
|
@@ -47,7 +58,8 @@ def from_stream(stream, close=True): | |
|
||
|
||
def from_file(file_path, encoding='utf-8'): | ||
"""Load a todo list from a file. | ||
""" | ||
Load a todo list from a file. | ||
|
||
:param str file_path: Path to the file | ||
:param str encoding: The encoding of the file to open | ||
|
@@ -56,26 +68,43 @@ def from_file(file_path, encoding='utf-8'): | |
if not os.path.isfile(file_path): | ||
raise FileNotFoundError('File doesn\'t exists: ' + file_path) | ||
|
||
stream = open(file_path, 'r', encoding=encoding) | ||
stream = io.open(file_path, 'r', encoding=encoding) | ||
|
||
return from_stream(stream) | ||
|
||
|
||
def from_string(string): | ||
"""Load a todo list from a string. | ||
""" | ||
Load a todo list from a string. | ||
|
||
:param str string: The string to parse | ||
:rtype: list | ||
""" | ||
|
||
# init | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm sorry but the comments you added in this function doesn't add any useful information, given the statements are simple enough to understand. Please remove them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you are right. the comments don't add any functionality. It is just a question of style and readability in general (which is in the end a question of personal taste). I can remove it. |
||
todos = [] | ||
|
||
|
||
|
||
# | ||
# evaluate each line | ||
# | ||
|
||
for line in string.strip().splitlines(): | ||
|
||
line = line.strip() | ||
|
||
todo_pre_data = todo_data_regex.match(line) | ||
|
||
todo = Todo() | ||
|
||
|
||
|
||
|
||
# | ||
# evaluate prefix data | ||
# | ||
|
||
if todo_pre_data: | ||
todo.completed = todo_pre_data.group(1) == 'x' | ||
|
||
|
@@ -85,43 +114,57 @@ def from_string(string): | |
if todo_pre_data.group(2): | ||
todo.completion_date = todo_pre_data.group(2) | ||
else: | ||
todo.creation_date = todo_pre_data.group(2) | ||
todo.creation_date = todo_pre_data.group(4) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well that's working ATM, maybe the regex is wrong. Better check the regex first. Anyway I never had issues with creation dates. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please check with the following code (just open the Python interpreter and paste the code in. I did it for both, Python v2.7 and Python v3.6): import re
todo_data_regex = re.compile( \
'^(?:(x) )?' + \
'(?:(\d{4}-\d{2}-\d{2}) )?' + \
'(?:\(([A-Z])\) )?' + \
'(?:(\d{4}-\d{2}-\d{2}) )?' \
)
match = todo_data_regex.match( \
'(A) 2018-03-28 an unfinished task created at 2018-03-28 +TestProject @Home due:2018-03-29')
print( \
'the complete prefix: ' + str(match.group(0)) + '\n' + \
'the COMPLETED FLAG: ' + str(match.group(1)) + '\n' + \
'the COMPLETED DATE: ' + str(match.group(2)) + '\n' + \
'the PRIORITY: ' + str(match.group(3)) + '\n' + \
'the CREATION DATE: ' + str(match.group(4)) \
) in both versions, the creation date should appear in |
||
|
||
todo.priority = todo_pre_data.group(3) | ||
|
||
text = todo_data_regex.sub('', line).strip() | ||
else: | ||
text = line | ||
|
||
todo_projects = todo_project_regex.findall(text) | ||
|
||
|
||
# | ||
# evaluate remaining data | ||
# | ||
|
||
# projects | ||
todo_projects = todo_project_regex.findall(text) | ||
if len(todo_projects) > 0: | ||
todo.projects = todo_projects | ||
text = todo_project_regex.sub('', text).strip() | ||
|
||
# contexts | ||
todo_contexts = todo_context_regex.findall(text) | ||
|
||
if len(todo_contexts) > 0: | ||
todo.contexts = todo_contexts | ||
text = todo_context_regex.sub('', text).strip() | ||
|
||
# tags | ||
todo_tags = todo_tag_regex.findall(text) | ||
|
||
if len(todo_tags) > 0: | ||
for todo_tag in todo_tags: | ||
todo.tags[todo_tag[0]] = todo_tag[1] | ||
|
||
text = todo_tag_regex.sub('', text).strip() | ||
|
||
# text | ||
todo.text = text | ||
|
||
|
||
|
||
# | ||
# add this TODO to list of todos | ||
# | ||
|
||
todos.append(todo) | ||
|
||
return todos | ||
|
||
|
||
def to_dicts(todos): | ||
"""Convert a list of :class:`todotxtio.Todo` objects to a list of todo dict. | ||
""" | ||
Convert a list of :class:`todotxtio.Todo` objects to a list of todo dict. | ||
|
||
:param list todos: List of :class:`todotxtio.Todo` objects | ||
:rtype: list | ||
|
@@ -130,7 +173,8 @@ def to_dicts(todos): | |
|
||
|
||
def to_stream(stream, todos, close=True): | ||
"""Write a list of todos to an already-opened stream. | ||
""" | ||
Write a list of todos to an already-opened stream. | ||
|
||
:param file stream: A file-like object | ||
:param list todos: List of :class:`todotxtio.Todo` objects | ||
|
@@ -144,28 +188,31 @@ def to_stream(stream, todos, close=True): | |
|
||
|
||
def to_file(file_path, todos, encoding='utf-8'): | ||
"""Write a list of todos to a file. | ||
""" | ||
Write a list of todos to a file. | ||
|
||
:param str file_path: Path to the file | ||
:param list todos: List of :class:`todotxtio.Todo` objects | ||
:param str encoding: The encoding of the file to open | ||
:rtype: None | ||
""" | ||
stream = open(file_path, 'w', encoding=encoding) | ||
stream = io.open(file_path, 'w', encoding=encoding) | ||
to_stream(stream, todos) | ||
|
||
|
||
def to_string(todos): | ||
"""Convert a list of todos to a string. | ||
""" | ||
Convert a list of todos to a string. | ||
|
||
:param list todos: List of :class:`todotxtio.Todo` objects | ||
:rtype: str | ||
""" | ||
return '\n'.join([str(todo) for todo in todos]) | ||
|
||
|
||
class Todo: | ||
"""Represent one todo. | ||
class Todo(object): | ||
""" | ||
Represent one todo. | ||
|
||
:param str text: The text of the todo | ||
:param bool completed: Should this todo be marked as completed? | ||
|
@@ -199,7 +246,8 @@ def __init__(self, text=None, completed=False, completion_date=None, priority=No | |
self.tags = tags | ||
|
||
def to_dict(self): | ||
"""Return a dict representation of this Todo instance. | ||
""" | ||
Return a dict representation of this Todo instance. | ||
|
||
:rtype: dict | ||
""" | ||
|
@@ -217,29 +265,31 @@ def to_dict(self): | |
def __setattr__(self, name, value): | ||
if name == 'completed': | ||
if not value: | ||
super().__setattr__('completion_date', None) # Uncompleted todo must not have any completion date | ||
super(Todo, self).__setattr__('completion_date', None) # Uncompleted todo must not have any completion date | ||
elif name == 'completion_date': | ||
if value: | ||
super().__setattr__('completed', True) # Setting the completion date must set this todo as completed... | ||
super(Todo, self).__setattr__('completed', True) # Setting the completion date must set this todo as completed... | ||
else: | ||
super().__setattr__('completed', False) # ...and vice-versa | ||
super(Todo, self).__setattr__('completed', False) # ...and vice-versa | ||
elif name in ['projects', 'contexts']: | ||
if not value: | ||
super().__setattr__(name, []) # Force contexts, projects to be lists when setting them to a falsely value | ||
super(Todo, self).__setattr__(name, []) # Force contexts, projects to be lists when setting them to a falsely value | ||
return | ||
elif type(value) is not list: # Make sure, otherwise, that the provided value is a list | ||
raise ValueError(name + ' should be a list') | ||
elif name == 'tags': | ||
if not value: | ||
super().__setattr__(name, {}) # Force tags to be a dict when setting them to a falsely value | ||
super(Todo, self).__setattr__(name, {}) # Force tags to be a dict when setting them to a falsely value | ||
return | ||
elif type(value) is not dict: # Make sure, otherwise, that the provided value is a dict | ||
raise ValueError(name + ' should be a dict') | ||
|
||
super().__setattr__(name, value) | ||
super(Todo, self).__setattr__(name, value) | ||
|
||
def __str__(self): | ||
"""Convert this Todo object in a valid Todo.txt line.""" | ||
""" | ||
Convert this Todo object in a valid Todo.txt line. | ||
""" | ||
ret = [] | ||
|
||
if self.completed: | ||
|
@@ -268,12 +318,15 @@ def __str__(self): | |
return ' '.join(ret) | ||
|
||
def __repr__(self): | ||
"""Call the __str__ method to return a textual representation of this Todo object.""" | ||
""" | ||
Call the __str__ method to return a textual representation of this Todo object. | ||
""" | ||
return self.__str__() | ||
|
||
|
||
def search(todos, text=None, completed=None, completion_date=None, priority=None, creation_date=None, projects=None, contexts=None, tags=None): | ||
"""Return a list of todos that matches the provided filters. | ||
""" | ||
Return a list of todos that matches the provided filters. | ||
|
||
It takes the exact same parameters as the :class:`todotxtio.Todo` object constructor, and return a list of :class:`todotxtio.Todo` objects as well. | ||
All criteria defaults to ``None`` which means that the criteria is ignored. | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file isn't meant to be executable. What is the purpose of this line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmmm... I always create my librarys so that in the end, they can be used as libraries AND can be executed as standalone tools (where this makes sense). So, you are right, here the line makes no sense, yet.