[hamster-applet] refactored parse_activity_input into Fact class. WARNING: possible breakages (did unit tests on pars
- From: Toms Baugis <tbaugis src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [hamster-applet] refactored parse_activity_input into Fact class. WARNING: possible breakages (did unit tests on pars
- Date: Sat, 21 Aug 2010 20:39:28 +0000 (UTC)
commit b220140b7b0fb041be53b8ddcba7cdabe47b5767
Author: Toms Bauģis <toms baugis gmail com>
Date: Sat Aug 21 21:39:18 2010 +0100
refactored parse_activity_input into Fact class. WARNING: possible breakages (did unit tests on parsing though but there are many bits to check)
src/hamster-cli | 4 +-
src/hamster-time-tracker | 66 +++++++------
src/hamster/applet.py | 65 +++++++------
src/hamster/client.py | 76 +++++++---------
src/hamster/db.py | 78 +++++-----------
src/hamster/edit_activity.py | 37 ++-----
src/hamster/overview_activities.py | 24 ++---
src/hamster/storage.py | 17 +--
src/hamster/utils/stuff.py | 168 ++++++++++++++++++++--------------
src/hamster/utils/trophies.py | 30 +++----
src/hamster/widgets/activityentry.py | 31 +++---
tests/stuff_test.py | 76 ++++++++-------
12 files changed, 321 insertions(+), 351 deletions(-)
---
diff --git a/src/hamster-cli b/src/hamster-cli
index 3fc2c9d..394cb10 100755
--- a/src/hamster-cli
+++ b/src/hamster-cli
@@ -45,7 +45,9 @@ class HamsterClient(object):
def start_tracking(self, activity, start_time = None, end_time = None):
'''Start a new activity.'''
- self.storage.add_fact(activity, start_time = start_time, end_time = end_time)
+ self.storage.add_fact(stuff.Fact(activity,
+ start_time = start_time,
+ end_time = end_time))
def stop_tracking(self):
diff --git a/src/hamster-time-tracker b/src/hamster-time-tracker
index 946eaf4..cd396a4 100755
--- a/src/hamster-time-tracker
+++ b/src/hamster-time-tracker
@@ -259,12 +259,12 @@ class ProjectHamster(object):
def on_today_row_activated(self, tree, path, column):
fact = tree.get_selected_fact()
-
- if fact:
- runtime.storage.add_fact(fact["name"],
- ", ".join(fact["tags"]),
- category_name = fact["category"],
- description = fact["description"])
+ fact = stuff.Fact(fact["name"],
+ tags = ", ".join(fact["tags"]),
+ category = fact["category"],
+ description = fact["description"])
+ if fact.activity:
+ runtime.storage.add_fact(fact)
"""button events"""
@@ -328,24 +328,24 @@ class ProjectHamster(object):
# first try to look up activity by desktop name
mapping = conf.get("workspace_mapping")
- parsed_activity = None
+ fact = None
if new < len(mapping):
- parsed_activity = stuff.parse_activity_input(mapping[new])
+ fact = stuff.Fact(mapping[new])
- if parsed_activity:
- category_id = None
- if parsed_activity.category_name:
- category_id = runtime.storage.get_category_id(parsed_activity.category_name)
+ if fact.activity:
+ category_id = None
+ if fact.category:
+ category_id = runtime.storage.get_category_id(fact.category)
- activity = runtime.storage.get_activity_by_name(parsed_activity.activity_name,
- category_id,
- ressurect = False)
- if activity:
- # we need dict below
- activity = dict(name = activity['name'],
- category = activity['category'],
- description = parsed_activity.description,
- tags = parsed_activity.tags)
+ activity = runtime.storage.get_activity_by_name(fact.activity,
+ category_id,
+ ressurect = False)
+ if activity:
+ # we need dict below
+ activity = dict(name = activity['name'],
+ category = activity['category'],
+ description = fact.description,
+ tags = fact.tags)
if not activity and "memory" in self.workspace_tracking:
@@ -366,10 +366,11 @@ class ProjectHamster(object):
return
# ok, switch
- runtime.storage.add_fact(activity['name'],
- ", ".join(activity['tags']),
- category_name = activity['category'],
- description = activity['description'])
+ fact = stuff.Fact(activity['name'],
+ tags = ", ".join(activity['tags']),
+ category = activity['category'],
+ description = activity['description']);
+ runtime.storage.add_fact(fact)
if self.notification:
self.notification.update(_("Changed activity"),
@@ -403,11 +404,15 @@ class ProjectHamster(object):
self.get_widget("switch_activity").set_sensitive(widget.get_text() != "")
def on_switch_activity_clicked(self, widget):
- if not self.new_name.get_text():
- return False
+ activity, temporary = self.new_name.get_value()
+
+ fact = stuff.Fact(activity,
+ tags = self.new_tags.get_text().decode("utf8", "replace"),
+ temporary = temporary)
+ if not fact.activity:
+ return
- runtime.storage.add_fact(self.new_name.get_text().decode("utf8", "replace"),
- self.new_tags.get_text().decode("utf8", "replace"))
+ runtime.storage.add_fact(fact)
self.new_name.set_text("")
self.new_tags.set_text("")
@@ -515,7 +520,8 @@ if __name__ == "__main__":
gtk.gdk.threads_init()
gtk.window_set_default_icon_name("hamster-applet")
- from hamster.utils import stuff, widgets, idle, trophies
+ from hamster import widgets, idle
+ from hamster.utils import stuff, trophies
try:
import wnck
diff --git a/src/hamster/applet.py b/src/hamster/applet.py
index d747202..ecc51f2 100755
--- a/src/hamster/applet.py
+++ b/src/hamster/applet.py
@@ -508,12 +508,13 @@ class HamsterApplet(object):
def on_today_row_activated(self, tree, path, column):
fact = tree.get_selected_fact()
+ fact = stuff.Fact(fact["name"],
+ tags = ", ".join(fact["tags"]),
+ category = fact["category"],
+ description = fact["description"])
- if fact:
- runtime.storage.add_fact(fact["name"],
- ", ".join(fact["tags"]),
- category_name = fact["category"],
- description = fact["description"])
+ if fact.activity:
+ runtime.storage.add_fact(fact)
self.__show_toggle(False)
@@ -587,30 +588,29 @@ class HamsterApplet(object):
# on switch, update our mapping between spaces and activities
self.workspace_activities[prev] = self.last_activity
-
activity = None
if "name" in self.workspace_tracking:
# first try to look up activity by desktop name
mapping = conf.get("workspace_mapping")
- parsed_activity = None
+ fact = None
if new < len(mapping):
- parsed_activity = stuff.parse_activity_input(mapping[new])
+ fact = stuff.Fact(mapping[new])
- if parsed_activity:
- category_id = None
- if parsed_activity.category_name:
- category_id = runtime.storage.get_category_id(parsed_activity.category_name)
+ if fact.activity:
+ category_id = None
+ if fact.category:
+ category_id = runtime.storage.get_category_id(fact.category)
- activity = runtime.storage.get_activity_by_name(parsed_activity.activity_name,
- category_id,
- resurrect = False)
- if activity:
- # we need dict below
- activity = dict(name = activity['name'],
- category = activity['category'],
- description = parsed_activity.description,
- tags = parsed_activity.tags)
+ activity = runtime.storage.get_activity_by_name(fact.activity,
+ category_id,
+ ressurect = False)
+ if activity:
+ # we need dict below
+ activity = dict(name = activity['name'],
+ category = activity['category'],
+ description = fact.description,
+ tags = fact.tags)
if not activity and "memory" in self.workspace_tracking:
@@ -631,10 +631,11 @@ class HamsterApplet(object):
return
# ok, switch
- runtime.storage.add_fact(activity['name'],
- ", ".join(activity['tags']),
- category_name = activity['category'],
- description = activity['description'])
+ fact = stuff.Fact(activity['name'],
+ tags = ", ".join(activity['tags']),
+ category = activity['category'],
+ description = activity['description']);
+ runtime.storage.add_fact(fact)
if self.notification:
self.notification.update(_("Changed activity"),
@@ -668,13 +669,15 @@ class HamsterApplet(object):
self.get_widget("switch_activity").set_sensitive(widget.get_text() != "")
def on_switch_activity_clicked(self, widget):
- activity_name, temporary = self.new_name.get_value()
- if not activity_name:
- return False
+ activity, temporary = self.new_name.get_value()
+
+ fact = stuff.Fact(activity,
+ tags = self.new_tags.get_text().decode("utf8", "replace"),
+ temporary = temporary)
+ if not fact.activity:
+ return
- runtime.storage.add_fact(self.new_name.get_text().decode("utf8", "replace"),
- self.new_tags.get_text().decode("utf8", "replace"),
- temporary = temporary)
+ runtime.storage.add_fact(fact)
self.new_name.set_text("")
self.new_tags.set_text("")
self.__show_toggle(False)
diff --git a/src/hamster/client.py b/src/hamster/client.py
index 6fad885..007240b 100644
--- a/src/hamster/client.py
+++ b/src/hamster/client.py
@@ -23,7 +23,8 @@ import datetime as dt
from calendar import timegm
import dbus, dbus.mainloop.glib
import gobject
-from utils.trophies import checker
+from utils import stuff, trophies
+
def from_dbus_fact(fact):
@@ -162,39 +163,37 @@ class Storage(gobject.GObject):
"""returns fact by it's ID"""
return from_dbus_fact(self.conn.GetFact(id))
- def add_fact(self, activity_name, tags = '', start_time = None, end_time = None,
- category_name = None, description = None,
- temporary = False):
- """Add fact. activity name can use `[-]start_time[-end_time] activity category, description #tag1 #tag2`
+ def add_fact(self, fact):
+ """Add fact. activity name can use the
+ `[-]start_time[-end_time] activity category, description #tag1 #tag2`
syntax, or params can be stated explicitly.
- Params, except for the start and end times will take precedence over
- derived values.
+ Params will take precedence over the derived values.
start_time defaults to current moment.
"""
+ if not fact.activity:
+ return None
+
+ fact.start_time = fact.start_time or dt.datetime.now()
+ serialized = fact.serialized_name()
- start_timestamp = start_time or 0
+ start_timestamp = fact.start_time or 0
if start_timestamp:
- start_timestamp = timegm(start_time.timetuple())
+ start_timestamp = timegm(fact.start_time.timetuple())
- end_timestamp = end_time or 0
+ end_timestamp = fact.end_time or 0
if end_timestamp:
- end_timestamp = timegm(end_time.timetuple())
-
- if isinstance(tags, list): #make sure we send what storage expects
- tags = ", ".join(tags)
- tags = tags or ''
-
- category_name = category_name or ''
- description = description or ''
+ end_timestamp = timegm(fact.end_time.timetuple())
- new_id = self.conn.AddFact(activity_name, tags, start_timestamp, end_timestamp, category_name, description, temporary)
+ new_id = self.conn.AddFact(serialized,
+ start_timestamp,
+ end_timestamp,
+ fact.temporary)
# TODO - the parsing should happen just once and preferably here
# we should feed (serialized_activity, start_time, end_time) into AddFact and others
if new_id:
- checker.check_fact_based(activity_name, tags, start_time, end_time, category_name, description)
-
+ trophies.checker.check_fact_based(fact)
return new_id
def stop_tracking(self, end_time = None):
@@ -207,36 +206,27 @@ class Storage(gobject.GObject):
"delete fact from database"
self.conn.RemoveFact(fact_id)
- def update_fact(self, fact_id, activity_name, tags = None,
- start_time = None, end_time = None,
- category_name = None, description = None, temporary = False):
+ def update_fact(self, fact_id, fact):
"""Update fact values. See add_fact for rules.
Update is performed via remove/insert, so the
fact_id after update should not be used anymore. Instead use the ID
from the fact dict that is returned by this function"""
- category_name = category_name or '' # so to override None
- description = description or '' # so to override None
-
- start_time = start_time or 0
- if start_time:
- start_time = timegm(start_time.timetuple())
-
- end_time = end_time or 0
- if end_time:
- end_time = timegm(end_time.timetuple())
+ start_time = fact.start_time or 0
+ if fact.start_time:
+ start_time = timegm(fact.start_time.timetuple())
- if tags and isinstance(tags, list):
- tags = ", ".join(tags)
- tags = tags or ''
+ end_time = fact.end_time or 0
+ if fact.end_time:
+ end_time = timegm(fact.end_time.timetuple())
- new_id = self.conn.UpdateFact(fact_id, activity_name, tags,
- start_time, end_time,
- category_name, description, temporary)
+ new_id = self.conn.UpdateFact(fact_id,
+ fact.serialized_name(),
+ start_time,
+ end_time,
+ fact.temporary)
- checker.check_update_based(fact_id, new_id, activity_name, tags,
- start_time, end_time,
- category_name, description)
+ trophies.checker.check_update_based(fact_id, new_id, fact)
return new_id
diff --git a/src/hamster/db.py b/src/hamster/db.py
index 1925d7f..3afbb74 100644
--- a/src/hamster/db.py
+++ b/src/hamster/db.py
@@ -35,13 +35,13 @@ except ImportError:
import os, time
import datetime
import storage
-from utils import stuff
from shutil import copy as copyfile
+import itertools
import datetime as dt
import gio
from xdg.BaseDirectory import xdg_data_home
-import itertools
+from utils import stuff, trophies
class Storage(storage.Storage):
con = None # Connection will be created on demand
@@ -74,11 +74,7 @@ class Storage(storage.Storage):
self.dispatch_overwrite()
# plan "b" â?? synchronize the time tracker's database from external source while the tracker is running
- try:
- import trophies
- trophies.unlock("plan_b")
- except:
- pass
+ trophies.unlock("plan_b")
self.__database_file = gio.File(self.db_path)
@@ -493,36 +489,20 @@ class Storage(storage.Storage):
(start_time, fact["id"]))
- def __add_fact(self, activity_name, tags, start_time = None,
- end_time = None, category_name = None,
- description = None, temporary = False):
+ def __add_fact(self, activity_name, start_time, end_time = None, temporary = False):
+ fact = stuff.Fact(activity_name,
+ start_time = start_time,
+ end_time = end_time,
+ temporary = temporary)
- activity = stuff.parse_activity_input(activity_name)
-
- # make sure that we do have an activity name after parsing
- if not activity.activity_name:
+ if not fact.activity or start_time is None: # sanity check
return 0
- # explicitly stated takes precedence
- activity.description = description or activity.description
-
- tags = [tag.strip() for tag in tags.split(",") if tag.strip()] # split by comma
- tags = tags or activity.tags # explicitly stated tags take priority
-
- tags = [dict(zip(('id', 'name', 'autocomplete'), row)) for row in self.GetTagIds(tags)] #this will create any missing tags too
-
-
- if category_name:
- activity.category_name = category_name
- if description:
- activity.description = description #override
- start_time = activity.start_time or start_time or datetime.datetime.now()
+ # get tags from database - this will create any missing tags too
+ tags = [dict(zip(('id', 'name', 'autocomplete'), row))
+ for row in self.GetTagIds(fact.tags)]
- start_time = start_time.replace(microsecond = 0)
- end_time = activity.end_time or end_time
- if end_time:
- end_time = end_time.replace(microsecond = 0)
now = datetime.datetime.now()
# if in future - roll back to past
@@ -537,29 +517,25 @@ class Storage(storage.Storage):
end_time -= dt.timedelta(days = 1)
- if not start_time or not activity.activity_name: # sanity check
- return None
-
# now check if maybe there is also a category
category_id = None
- if activity.category_name:
- category_id = self.__get_category_id(activity.category_name)
+ if fact.category:
+ category_id = self.__get_category_id(fact.category)
if not category_id:
- category_id = self.__add_category(activity.category_name)
+ category_id = self.__add_category(fact.category)
# try to find activity, resurrect if not temporary
- activity_id = self.__get_activity_by_name(activity.activity_name,
+ activity_id = self.__get_activity_by_name(fact.activity,
category_id,
resurrect = not temporary)
if not activity_id:
- activity_id = self.__add_activity(activity.activity_name,
+ activity_id = self.__add_activity(fact.activity,
category_id, temporary)
else:
activity_id = activity_id['id']
# if we are working on +/- current day - check the last_activity
if (dt.datetime.now() - start_time <= dt.timedelta(days=1)):
-
# pull in previous facts
facts = self.__get_todays_facts()
@@ -570,8 +546,8 @@ class Storage(storage.Storage):
if previous and previous['start_time'] < start_time:
# check if maybe that is the same one, in that case no need to restart
if previous["activity_id"] == activity_id \
- and previous["tags"] == sorted([tag["name"] for tag in tags]) \
- and (previous["description"] or "") == (description or ""):
+ and set(previous["tags"]) == set([tag["name"] for tag in tags]) \
+ and (previous["description"] or "") == (fact.description or ""):
return None
# otherwise, if no description is added
@@ -618,7 +594,7 @@ class Storage(storage.Storage):
INSERT INTO facts (activity_id, start_time, end_time, description)
VALUES (?, ?, ?, ?)
"""
- self.execute(insert, (activity_id, start_time, end_time, activity.description))
+ self.execute(insert, (activity_id, start_time, end_time, fact.description))
fact_id = self.__last_insert_rowid()
@@ -792,11 +768,7 @@ class Storage(storage.Storage):
# Finished! - deleted an activity with more than 50 facts on it
if bound_facts >= 50:
- try:
- import trophies
- trophies.unlock("finished")
- except:
- pass
+ trophies.unlock("finished")
def __remove_category(self, id):
"""move all activities to unsorted and remove category"""
@@ -1200,12 +1172,8 @@ class Storage(storage.Storage):
self.execute("UPDATE version SET version = %d" % current_version)
print "updated database from version %d to %d" % (version, current_version)
- try:
- # oldtimer â?? database version structure had been performed on startup (thus we know that he has been on at least 2 versions)
- import trophies
- trophies.unlock("oldtimer")
- except:
- pass
+ # oldtimer â?? database version structure had been performed on startup (thus we know that he has been on at least 2 versions)
+ trophies.unlock("oldtimer")
"""we start with an empty database and then populate with default
diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py
index d80fb7b..e23919f 100644
--- a/src/hamster/edit_activity.py
+++ b/src/hamster/edit_activity.py
@@ -190,40 +190,25 @@ class CustomFactController:
def on_save_button_clicked(self, button):
activity_name, temporary = self.new_name.get_value()
- parsed = stuff.parse_activity_input(activity_name)
-
- if not parsed.activity_name:
- return False
-
- # explicit takes precedence
- description = self.figure_description()
-
- tags = self.new_tags.get_text().decode('utf8')
-
- start_time = self._get_datetime("start")
if self.get_widget("in_progress").get_active():
end_time = None
else:
end_time = self._get_datetime("end")
+ fact = stuff.Fact(activity_name,
+ description = self.figure_description(),
+ tags = self.new_tags.get_text().decode('utf8'),
+ start_time = self._get_datetime("start"),
+ end_time = end_time,
+ temporary = temporary)
+ if not fact.activity:
+ return False
+
if self.fact_id:
- runtime.storage.update_fact(self.fact_id,
- activity_name,
- tags,
- start_time,
- end_time,
- parsed.category_name,
- description,
- temporary = temporary)
+ runtime.storage.update_fact(self.fact_id, fact)
else:
- runtime.storage.add_fact(activity_name,
- tags,
- start_time,
- end_time,
- parsed.category_name,
- description,
- temporary = temporary)
+ runtime.storage.add_fact(fact)
self.close_window()
diff --git a/src/hamster/overview_activities.py b/src/hamster/overview_activities.py
index 2d0dffe..70b09b0 100644
--- a/src/hamster/overview_activities.py
+++ b/src/hamster/overview_activities.py
@@ -165,24 +165,18 @@ class OverviewBox(gtk.VBox):
else:
selected_date = fact["date"]
- fact = stuff.parse_activity_input(text.decode("utf-8"))
+ fact = stuff.Fact(text.decode("utf-8"))
- if not all((fact.activity_name, fact.start_time, fact.end_time)):
+ if not all((fact.activity, fact.start_time, fact.end_time)):
return
- start_time = fact.start_time.replace(year = selected_date.year,
- month = selected_date.month,
- day = selected_date.day)
- end_time = fact.end_time.replace(year = selected_date.year,
- month = selected_date.month,
- day = selected_date.day)
-
- new_id = runtime.storage.add_fact(fact.activity_name,
- ", ".join(fact.tags),
- start_time,
- end_time,
- fact.category_name,
- fact.description)
+ fact.start_time = fact.start_time.replace(year = selected_date.year,
+ month = selected_date.month,
+ day = selected_date.day)
+ fact.end_time = fact.end_time.replace(year = selected_date.year,
+ month = selected_date.month,
+ day = selected_date.day)
+ new_id = runtime.storage.add_fact(fact)
# You can do that?! - copy/pasted an activity
trophies.unlock("can_do_that")
diff --git a/src/hamster/storage.py b/src/hamster/storage.py
index 91baf6f..b7e7159 100644
--- a/src/hamster/storage.py
+++ b/src/hamster/storage.py
@@ -104,10 +104,8 @@ class Storage(dbus.service.Object):
self.ToggleCalled()
# facts
- @dbus.service.method("org.gnome.Hamster", in_signature='ssiissb', out_signature='i')
- def AddFact(self, activity_name, tags, start_time, end_time,
- category_name = None, description = None, temporary = False):
-
+ @dbus.service.method("org.gnome.Hamster", in_signature='siib', out_signature='i')
+ def AddFact(self, fact, start_time, end_time, temporary = False):
start_time = start_time or None
if start_time:
start_time = dt.datetime.utcfromtimestamp(start_time)
@@ -117,7 +115,7 @@ class Storage(dbus.service.Object):
end_time = dt.datetime.utcfromtimestamp(end_time)
self.start_transaction()
- result = self.__add_fact(activity_name, tags, start_time, end_time, category_name, description, temporary)
+ result = self.__add_fact(fact, start_time, end_time, temporary)
self.end_transaction()
if result:
@@ -135,10 +133,8 @@ class Storage(dbus.service.Object):
return to_dbus_fact(fact)
- @dbus.service.method("org.gnome.Hamster", in_signature='issiissb', out_signature='i')
- def UpdateFact(self, fact_id, activity_name, tags,
- start_time, end_time,
- category_name = None, description = None, temporary = False):
+ @dbus.service.method("org.gnome.Hamster", in_signature='isiib', out_signature='i')
+ def UpdateFact(self, fact_id, fact, start_time, end_time, temporary = False):
if start_time:
start_time = dt.datetime.utcfromtimestamp(start_time)
else:
@@ -151,8 +147,7 @@ class Storage(dbus.service.Object):
self.start_transaction()
self.__remove_fact(fact_id)
- result = self.__add_fact(activity_name, tags, start_time, end_time,
- category_name, description, temporary)
+ result = self.__add_fact(fact, start_time, end_time, temporary)
self.end_transaction()
diff --git a/src/hamster/utils/stuff.py b/src/hamster/utils/stuff.py
index bcff4d1..8f5cf4c 100644
--- a/src/hamster/utils/stuff.py
+++ b/src/hamster/utils/stuff.py
@@ -246,75 +246,103 @@ def figure_time(str_time):
return dt.datetime.now().replace(hour = hours, minute = minutes,
second = 0, microsecond = 0)
-def parse_activity_input(text):
- """Currently pretty braindead function that tries to parse arbitrary input
- into a activity"""
- class InputParseResult(object):
- def __init__(self):
- self.activity_name = None
- self.category_name = None
- self.start_time = None
- self.end_time = None
- self.description = None
- self.tags = []
- self.ponies = False
-
-
- res = InputParseResult()
-
- input_parts = text.strip().split(" ")
- if len(input_parts) > 1 and re.match('^-?\d', input_parts[0]): #look for time only if there is more
- potential_time = text.split(" ")[0]
- potential_end_time = None
- if len(potential_time) > 1 and potential_time.startswith("-"):
- #if starts with minus, treat as minus delta minutes
- res.start_time = dt.datetime.now() + dt.timedelta(minutes =
- int(potential_time))
-
- else:
- if potential_time.find("-") > 0:
- potential_time, potential_end_time = potential_time.split("-", 2)
- res.end_time = figure_time(potential_end_time)
-
- res.start_time = figure_time(potential_time)
-
- #remove parts that worked
- if res.start_time and potential_end_time and not res.end_time:
- res.start_time = None #scramble
- elif res.start_time:
- text = text[text.find(" ")+1:]
-
- #see if we have description of activity somewhere here (delimited by comma)
- if text.find(",") > 0:
- text, res.description = text.split(",", 1)
- res.description = res.description.strip()
-
- # more of a hidden feature for copy and paste purposes
- # tags are marked with hash sign and parsed only at the end of description
- words = res.description.split(" ")
- tags = []
- for word in reversed(words):
- if word.startswith("#"):
- tags.append(word.strip("#,")) #avoid commas
- else:
- break
-
- if tags:
- res.tags = tags
- res.description = " ".join(res.description.split(" ")[:-len(tags)])
-
+class Fact(object):
+ def __init__(self, activity, category = "", description = "", tags = "",
+ start_time = None, end_time = None, id = None,
+ temporary = False):
+ """the category, description and tags can be either passed in explicitly
+ or by using the "activity category, description #tag #tag" syntax.
+ explicitly stated values will take precedence over derived ones"""
+ self.original_activity = activity # unparsed version, mainly for trophies right now
+ self.activity = None
+ self.category = None
+ self.description = None
+ self.tags = None
+ self.start_time = None
+ self.end_time = None
+ self.temporary = temporary
+ self.id = id
+ self.ponies = False
+
+ # parse activity
+ input_parts = activity.strip().split(" ")
+ if len(input_parts) > 1 and re.match('^-?\d', input_parts[0]): #look for time only if there is more
+ potential_time = activity.split(" ")[0]
+ potential_end_time = None
+ if len(potential_time) > 1 and potential_time.startswith("-"):
+ #if starts with minus, treat as minus delta minutes
+ self.start_time = dt.datetime.now() + dt.timedelta(minutes = int(potential_time))
- if text.find("@") > 0:
- text, res.category_name = text.split("@", 1)
- res.category_name = res.category_name.strip()
-
- #only thing left now is the activity name itself
- res.activity_name = text.strip()
-
- #this is most essential
- if any([b in text for b in ("bbq", "barbeque", "barbecue")]) and "omg" in text:
- res.ponies = True
- res.description = "[ponies = 1], [rainbows = 0]"
-
- return res
+ else:
+ if potential_time.find("-") > 0:
+ potential_time, potential_end_time = potential_time.split("-", 2)
+ self.end_time = figure_time(potential_end_time)
+
+ self.start_time = figure_time(potential_time)
+
+ #remove parts that worked
+ if self.start_time and potential_end_time and not self.end_time:
+ self.start_time = None #scramble
+ elif self.start_time:
+ activity = activity[activity.find(" ")+1:]
+
+ #see if we have description of activity somewhere here (delimited by comma)
+ if activity.find(",") > 0:
+ activity, self.description = activity.split(",", 1)
+ self.description = self.description.strip()
+
+ # tags are marked with hash sign and parsed only at the end of description
+ # stop looking for tags if we stumble upon a non tag
+ words = self.description.split(" ")
+ parsed_tags = []
+ for word in reversed(words):
+ if word.startswith("#"):
+ parsed_tags.append(word.strip("#,")) #avoid commas
+ else:
+ break
+
+ if parsed_tags:
+ self.tags = parsed_tags
+ self.description = " ".join(self.description.split(" ")[:-len(self.tags)])
+
+ if activity.find("@") > 0:
+ activity, self.category = activity.split("@", 1)
+ self.category = self.category.strip()
+
+ #this is most essential
+ if any([b in activity for b in ("bbq", "barbeque", "barbecue")]) and "omg" in activity:
+ self.ponies = True
+ self.description = "[ponies = 1], [rainbows = 0]"
+
+ #only thing left now is the activity name itself
+ self.activity = activity.strip()
+
+ if tags and isinstance(tags, list):
+ tags = " ".join(tags)
+ tags = [tag.strip() for tag in tags.split(",") if tag.strip()]
+
+ # override implicit with explicit
+ self.category = category.replace("#", "").replace(",", "") or self.category or None
+ self.description = description.replace("#", "") or self.description or None
+ self.tags = tags or self.tags or None
+ self.start_time = start_time or self.start_time or None
+ self.end_time = end_time or self.end_time or None
+
+
+ def serialized_name(self):
+ res = self.activity
+
+ if self.category:
+ res += "@%s" % self.category
+
+ if self.description or self.tags:
+ res += ",%s %s" % (self.description or "",
+ " ".join(["#%s" % tag for tag in self.tags]))
+ return res
+
+ def __str__(self):
+ time = self.start_time.strftime("%d %m %Y %H:%M")
+ if self.end_time:
+ time = "%s - %s" % (time, self.end_time.strftime("%H:%M"))
+ return "%s %s" % (time, self.serialized_name())
diff --git a/src/hamster/utils/trophies.py b/src/hamster/utils/trophies.py
index 70666a4..f4fd9cc 100644
--- a/src/hamster/utils/trophies.py
+++ b/src/hamster/utils/trophies.py
@@ -80,7 +80,7 @@ class Checker(object):
self.flags = {}
- def check_update_based(self, prev_id, new_id, activity_name, tags, start_time, end_time, category_name, description):
+ def check_update_based(self, prev_id, new_id, fact):
if not storage: return
if not self.flags.get('last_update_id') or prev_id != self.flags['last_update_id']:
@@ -95,7 +95,7 @@ class Checker(object):
unlock("all_wrong")
- def check_fact_based(self, activity_name, tags, start_time, end_time, category_name, description):
+ def check_fact_based(self, fact):
"""quite possibly these all could be called from the service as
there is bigger certainty as to what did actually happen"""
@@ -103,24 +103,18 @@ class Checker(object):
if not storage: return
# explicit over implicit
- fact = stuff.parse_activity_input(activity_name)
- if not fact.activity_name: # TODO - parse_activity could return None for these cases
+ if not fact.activity: # TODO - parse_activity could return None for these cases
return
# full plate - use all elements of syntax parsing
- if all((fact.category_name, fact.description, fact.tags, fact.start_time, fact.end_time)):
+ derived_fact = stuff.Fact(fact.original_activity)
+ if all((derived_fact.category, derived_fact.description,
+ derived_fact.tags, derived_fact.start_time, derived_fact.end_time)):
unlock("full_plate")
- fact.tags = [tag.strip() for tag in tags.split(",") if tag.strip()] or fact.tags
- fact.category_name = category_name or fact.category_name
- fact.description = description or fact.description
- fact.start_time = start_time or fact.start_time or dt.datetime.now()
- fact.end_time = end_time or fact.end_time
-
-
# Jumper - hidden - made 10 switches within an hour (radical)
- if not fact.end_time: #end time normally denotes switch
+ if not fact.end_time: # end time normally denotes switch
last_ten = self.flags.setdefault('last_ten_ongoing', [])
last_ten.append(fact)
last_ten = last_ten[-10:]
@@ -135,7 +129,7 @@ class Checker(object):
unlock("good_memory")
# layering - entered 4 activities in a row in one of previous days, each one overlapping the previous one
- # avoiding today as in that case the layering
+ # avoiding today as in that case the layering might be automotical
last_four = self.flags.setdefault('last_four', [])
last_four.append(fact)
last_four = last_four[-4:]
@@ -158,18 +152,18 @@ class Checker(object):
# alpha bravo charlie â?? used delta times to enter at least 50 activities
- if fact.start_time and activity_name.startswith("-"):
+ if fact.start_time and fact.original_activity.startswith("-"):
counter = increment("hamster-applet", "alpha_bravo_charlie")
if counter == 50:
unlock("alpha_bravo_charlie")
# cryptic - hidden - used word shorter than 4 letters for the activity name
- if len(fact.activity_name) < 4:
+ if len(fact.activity) < 4:
unlock("cryptic")
# madness â?? hidden â?? entered an activity in all caps
- if fact.activity_name == fact.activity_name.upper():
+ if fact.activity == fact.activity.upper():
unlock("madness")
# verbose - hidden - description longer than 5 words
@@ -189,7 +183,7 @@ class Checker(object):
# patrys complains about who's gonna garbage collect. should think
# about this
if not storage.check_achievement("hamster-applet", "ultra_focused"):
- activity_count = increment("hamster-applet", "focused_%s %s" % (fact.activity_name, fact.category_name or ""))
+ activity_count = increment("hamster-applet", "focused_%s %s" % (fact.activity, fact.category or ""))
# focused â?? 100 facts with single activity
if activity_count == 100:
unlock("hamster-applet", "focused")
diff --git a/src/hamster/widgets/activityentry.py b/src/hamster/widgets/activityentry.py
index 0ad67e9..fa0a6ac 100644
--- a/src/hamster/widgets/activityentry.py
+++ b/src/hamster/widgets/activityentry.py
@@ -133,15 +133,15 @@ class ActivityEntry(gtk.Entry):
if not self._parent_click_watcher:
self._parent_click_watcher = self.get_toplevel().connect("button-press-event", self._on_focus_out_event)
- activity = stuff.parse_activity_input(self.filter)
+ fact = stuff.Fact(self.filter)
time = ''
- if activity.start_time:
- time = activity.start_time.strftime("%H:%M")
- if activity.end_time:
- time += "-%s" % activity.end_time.strftime("%H:%M")
+ if fact.start_time:
+ time = fact.start_time.strftime("%H:%M")
+ if fact.end_time:
+ time += "-%s" % fact.end_time.strftime("%H:%M")
- self.time_icon_column.set_visible(activity.start_time != None and self.filter.find("@") == -1)
- self.time_column.set_visible(activity.start_time != None and self.filter.find("@") == -1)
+ self.time_icon_column.set_visible(fact.start_time is not None and self.filter.find("@") == -1)
+ self.time_column.set_visible(fact.start_time is not None and self.filter.find("@") == -1)
self.category_column.set_visible(self.filter.find("@") == -1)
@@ -179,7 +179,6 @@ class ActivityEntry(gtk.Entry):
def complete_inline(self):
model = self.tree.get_model()
- activity = stuff.parse_activity_input(self.filter)
subject = self.get_text()
if not subject or model.iter_n_children(None) == 0:
@@ -218,21 +217,21 @@ class ActivityEntry(gtk.Entry):
return #same thing, no need to repopulate
self.filter = self.get_text().decode('utf8', 'replace')[:cursor]
- input_activity = stuff.parse_activity_input(self.filter)
+ fact = stuff.Fact(self.filter)
# do not cache as ordering and available options change over time
- self.activities = runtime.storage.get_activities(input_activity.activity_name)
- self.external_activities = self.external.get_activities(input_activity.activity_name)
+ self.activities = runtime.storage.get_activities(fact.activity)
+ self.external_activities = self.external.get_activities(fact.activity)
self.activities.extend(self.external_activities)
self.categories = self.categories or runtime.storage.get_categories()
time = ''
- if input_activity.start_time:
- time = input_activity.start_time.strftime("%H:%M")
- if input_activity.end_time:
- time += "-%s" % input_activity.end_time.strftime("%H:%M")
+ if fact.start_time:
+ time = fact.start_time.strftime("%H:%M")
+ if fact.end_time:
+ time += "-%s" % fact.end_time.strftime("%H:%M")
store = self.tree.get_model()
@@ -248,7 +247,7 @@ class ActivityEntry(gtk.Entry):
fillable = (self.filter[:self.filter.find("@") + 1] + category['name'])
store.append([fillable, category['name'], fillable, time])
else:
- key = input_activity.activity_name.decode('utf8', 'replace').lower()
+ key = fact.activity.decode('utf8', 'replace').lower()
for activity in self.activities:
fillable = activity['name'].lower()
if activity['category']:
diff --git a/tests/stuff_test.py b/tests/stuff_test.py
index 6d602fa..47fc43f 100644
--- a/tests/stuff_test.py
+++ b/tests/stuff_test.py
@@ -3,74 +3,80 @@ import sys, os.path
sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), "..")))
import unittest
-from hamster import stuff
+from hamster.utils import stuff
class TestActivityInputParsing(unittest.TestCase):
def test_plain_name(self):
# plain activity name
- activity = stuff.parse_activity_input("just a simple case")
- self.assertEquals(activity.activity_name, "just a simple case")
- assert activity.category_name is None and activity.start_time is None \
- and activity.end_time is None and activity.category_name is None\
- and activity.description is None
+ activity = stuff.Fact("just a simple case")
+ self.assertEquals(activity.activity, "just a simple case")
+
+ assert activity.category is None
+ assert activity.start_time is None
+ assert activity.end_time is None
+ assert activity.category is None
+ assert activity.description is None
def test_with_start_time(self):
# with time
- activity = stuff.parse_activity_input("12:35 with start time")
- self.assertEquals(activity.activity_name, "with start time")
+ activity = stuff.Fact("12:35 with start time")
+ self.assertEquals(activity.activity, "with start time")
self.assertEquals(activity.start_time.strftime("%H:%M"), "12:35")
#rest must be empty
- assert activity.category_name is None \
- and activity.end_time is None and activity.category_name is None\
- and activity.description is None
+ assert activity.category is None
+ assert activity.end_time is None
+ assert activity.description is None
def test_with_start_and_end_time(self):
# with time
- activity = stuff.parse_activity_input("12:35-14:25 with start-end time")
- self.assertEquals(activity.activity_name, "with start-end time")
+ activity = stuff.Fact("12:35-14:25 with start-end time")
+ self.assertEquals(activity.activity, "with start-end time")
self.assertEquals(activity.start_time.strftime("%H:%M"), "12:35")
self.assertEquals(activity.end_time.strftime("%H:%M"), "14:25")
#rest must be empty
- assert activity.category_name is None \
- and activity.category_name is None\
- and activity.description is None
+ assert activity.category is None
+ assert activity.description is None
def test_category(self):
# plain activity name
- activity = stuff.parse_activity_input("just a simple case hamster")
- self.assertEquals(activity.activity_name, "just a simple case")
- self.assertEquals(activity.category_name, "hamster")
- assert activity.start_time is None \
- and activity.end_time is None \
- and activity.description is None
+ activity = stuff.Fact("just a simple case hamster")
+ self.assertEquals(activity.activity, "just a simple case")
+ self.assertEquals(activity.category, "hamster")
+ assert activity.start_time is None
+ assert activity.end_time is None
+ assert activity.description is None
def test_description(self):
# plain activity name
- activity = stuff.parse_activity_input("case, with added description")
- self.assertEquals(activity.activity_name, "case")
+ activity = stuff.Fact("case, with added description")
+ self.assertEquals(activity.activity, "case")
self.assertEquals(activity.description, "with added description")
- assert activity.category_name is None and activity.start_time is None \
- and activity.end_time is None and activity.category_name is None
+ assert activity.category is None
+ assert activity.start_time is None
+ assert activity.end_time is None
+ assert activity.category is None
def test_tags(self):
# plain activity name
- activity = stuff.parse_activity_input("case, with added #de description #and, #some #tags")
- self.assertEquals(activity.activity_name, "case")
+ activity = stuff.Fact("case, with added #de description #and, #some #tags")
+ self.assertEquals(activity.activity, "case")
self.assertEquals(activity.description, "with added #de description")
- self.assertEquals(activity.tags, ["tags", "some", "and"]) #the list is parsed from the other end
- assert activity.category_name is None and activity.start_time is None \
- and activity.end_time is None
+ self.assertEquals(set(activity.tags), set(["and", "some", "tags"]))
+ assert activity.category is None
+ assert activity.start_time is None
+ assert activity.end_time is None
def test_full(self):
# plain activity name
- activity = stuff.parse_activity_input("1225-1325 case cat, description")
+ activity = stuff.Fact("1225-1325 case cat, description #ta non-tag #tag #bag")
self.assertEquals(activity.start_time.strftime("%H:%M"), "12:25")
self.assertEquals(activity.end_time.strftime("%H:%M"), "13:25")
- self.assertEquals(activity.activity_name, "case")
- self.assertEquals(activity.category_name, "cat")
- self.assertEquals(activity.description, "description")
+ self.assertEquals(activity.activity, "case")
+ self.assertEquals(activity.category, "cat")
+ self.assertEquals(activity.description, "description #ta non-tag")
+ self.assertEquals(set(activity.tags), set(["bag", "tag"]))
if __name__ == '__main__':
unittest.main()
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]