Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mr custom #224

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions hamster_lib/backends/sqlalchemy/storage.py
Expand Up @@ -993,6 +993,12 @@ def _update(self, fact, raw=False):
self.store.logger.error(message)
raise ValueError(message)

# Check for valid time range.
if fact.start >= fact.end:
message = _('Invalid time range of {!r}. The start is large or equal than the end.'.format(fact))
self.store.logger.error(message)
raise ValueError(message)

facts_in_timeframe = self._get_all(fact.start, fact.end, partial=True)
# This works because the conditional gets evaluated from left to right.
# If ``facts_in_timeframe`` would be empty and hence would throw an
Expand Down
36 changes: 29 additions & 7 deletions hamster_lib/objects.py
Expand Up @@ -366,9 +366,10 @@ def __init__(self, activity, start, end=None, pk=None, description=None, tags=No
self.start = start
self.end = end
self.description = description
self.tags = set()
self.tags = []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific reason for this change?

if tags:
self.tags = set(tags)
tags = set(tags)
self.tags = [Tag(name = x) for x in tags]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whist you are correct that this should be a set of Tag instances the mistake is already in L356! The constructor should expect an iterable of such Tag instances, not strings. I created an issue about this.


@classmethod
def create_from_raw_fact(cls, raw_fact, config=None):
Expand Down Expand Up @@ -433,6 +434,16 @@ def at_split(string):
front, back = front.strip(), back.strip()
return (front, back)


def hashtag_split(string):
""" Return a list of tags marked with the hashtag.
"""
result = string.split('#')
result = [x.strip() for x in result]
result = [x.strip() for x in result if x != '']
return result


def comma_split(string):
"""
Split string at the most left comma.
Expand Down Expand Up @@ -468,7 +479,18 @@ def comma_split(string):
activity_name, back = at_split(rest)

if back:
category_name, description = comma_split(back)
# Check for an existing hashtag.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting approach. The next iteration of hamster-lib will however refactor the entire 'raw fact' parsing machinery in total, which is why no effort has been made to take care of tags.

hashtag_pos = back.find('#')
if hashtag_pos >= 0:
category_name = back[:hashtag_pos].strip()
back = back[hashtag_pos:]
tag_string, description = comma_split(back)
tags = hashtag_split(tag_string)
else:
category_name, description = comma_split(back)
tags = []


if category_name:
category = Category(category_name)
else:
Expand All @@ -477,7 +499,7 @@ def comma_split(string):
category, description = None, None

activity = Activity(activity_name, category=category)
return cls(activity, start, end=end, description=description)
return cls(activity, start, end=end, description=description, tags = tags)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there should not be kwarg assignment should not be surrounded by whitespace as per PEP8


@property
def start(self):
Expand Down Expand Up @@ -579,10 +601,10 @@ def get_string_delta(self, format='%M'):
"""
seconds = int(self.delta.total_seconds())
if format == '%M':
result = text_type(int(seconds / 60))
result = text_type(int(round(seconds / 60.)))
elif format == '%H:%M':
result = '{hours:02d}:{minutes:02d}'.format(hours=int(seconds / 3600),
minutes=int((seconds % 3600) / 60))
result = '{hours:02d}:{minutes:02d}'.format(hours=int(round(seconds / 3600.)),
minutes=int(round((seconds % 3600.) / 60.)))
else:
raise ValueError(_("Got invalid format argument."))
return result
Expand Down
73 changes: 73 additions & 0 deletions hamster_lib/reports.py
Expand Up @@ -200,6 +200,79 @@ def _write_fact(self, fact_tuple):
results.append(data)
self.csv_writer.writerow(results)

@python_2_unicode_compatible
class CSVWriter(ReportWriter):
def __init__(self, path):
"""
Initialize a new instance.

Besides our default behaviour we create a localized heading.
Also, we need to make sure that our heading is UTF-8 encoded on python 2!
In that case ``self.file`` will be openend in binary mode and ready to accept
those encoded headings.
"""
super(CSVWriter, self).__init__(path)
self.csv_writer = csv.writer(self.file, delimiter = str(','), quoting = csv.QUOTE_MINIMAL)
headers = (
_("start time"),
_("end time"),
_("activity"),
_("category"),
_("description"),
_("duration minutes"),
)
results = []
for h in headers:
data = text_type(h)
if sys.version_info < (3, 0):
data = data.encode('utf-8')
results.append(data)
self.csv_writer.writerow(results)

def _fact_to_tuple(self, fact):
"""
Convert a ``Fact`` to its normalized tuple.

This is where all type conversion for ``Fact`` attributes to strings as well
as any normalization happens.

Args:
fact (hamster_lib.Fact): Fact to be converted.

Returns:
FactTuple: Tuple representing the original ``Fact``.
"""
# Fields that may have ``None`` value will be represented by ''
if fact.category:
category = fact.category.name
else:
category = ''

description = fact.description or ''

return FactTuple(
start=fact.start.strftime(self.datetime_format),
end=fact.end.strftime(self.datetime_format),
activity=fact.activity.name,
duration=fact.get_string_delta('%M'),
category=text_type(category),
description=description,
)

def _write_fact(self, fact_tuple):
"""
Write a single fact.

On python 2 we need to make sure we encode our data accordingly so we can feed it to our
file object which in this case needs to be opened in binary mode.
"""
results = []
for value in fact_tuple:
data = text_type(value)
if sys.version_info < (3, 0):
data = data.encode('utf-8')
results.append(data)
self.csv_writer.writerow(results)

@python_2_unicode_compatible
class ICALWriter(ReportWriter):
Expand Down