Exemplo n.º 1
0
 def test_spaces(self):
     # cf. issue #114
     fact = Fact.parse("11:00 12:00 BPC-261 - Task title@Project#code")
     self.assertEqual(fact.activity, "BPC-261 - Task title")
     self.assertEqual(fact.category, "Project")
     self.assertEqual(fact.description, "")
     self.assertEqual(fact.tags, ["code"])
     # space between category and tag
     fact2 = Fact.parse("11:00 12:00 BPC-261 - Task title@Project #code")
     self.assertEqual(fact.serialized(), fact2.serialized())
Exemplo n.º 2
0
 def test_decimal_in_activity(self):
     # cf. issue #270
     fact = Fact.parse("12:25-13:25 10.0@ABC, Two Words #tag #bäg")
     self.assertEqual(fact.activity, "10.0")
     self.assertEqual(fact.category, "ABC")
     self.assertEqual(fact.description, "Two Words")
     # should not pick up a time here
     fact = Fact.parse("10.00@ABC, Two Words #tag #bäg")
     self.assertEqual(fact.activity, "10.00")
     self.assertEqual(fact.category, "ABC")
     self.assertEqual(fact.description, "Two Words")
Exemplo n.º 3
0
 def test_comparison(self):
     fact1 = Fact.parse("12:25-13:25 case@cat, description #tag #bäg")
     fact2 = fact1.copy()
     self.assertEqual(fact1, fact2)
     fact2 = fact1.copy()
     fact2.activity = "abcd"
     self.assertNotEqual(fact1, fact2)
     fact2 = fact1.copy()
     fact2.category = "abcd"
     self.assertNotEqual(fact1, fact2)
     fact2 = fact1.copy()
     fact2.description = "abcd"
     self.assertNotEqual(fact1, fact2)
     import datetime as dt
     fact2 = fact1.copy()
     fact2.start_time = hamster_now()
     self.assertNotEqual(fact1, fact2)
     fact2 = fact1.copy()
     fact2.end_time = hamster_now()
     self.assertNotEqual(fact1, fact2)
     # wrong order
     fact2 = fact1.copy()
     fact2.tags = ["bäg", "tag"]
     self.assertNotEqual(fact1, fact2)
     # correct order
     fact2 = fact1.copy()
     fact2.tags = ["tag", "bäg"]
     self.assertEqual(fact1, fact2)
Exemplo n.º 4
0
    def add_fact(self, fact, start_time=None, end_time=None, temporary=False):
        """Add fact.

        fact: either a Fact instance or
              a string that can be parsed through Fact.parse.

        note: start_time and end_time are used only when fact is a string,
              for backward compatibility.
              Passing fact as a string is deprecated
              and will be removed in a future version.
              Parsing should be done in the caller.
        """
        if isinstance(fact, str):
            logger.info("Passing fact as a string is deprecated")
            fact = Fact.parse(fact)
            fact.start_time = start_time
            fact.end_time = end_time

        self.start_transaction()
        result = self.__add_fact(fact, temporary)
        self.end_transaction()

        if result:
            self.facts_changed()
        return result
Exemplo n.º 5
0
 def test_category(self):
     # plain activity name
     activity = Fact.parse("just a simple case@hämster")
     self.assertEqual(activity.activity, "just a simple case")
     self.assertEqual(activity.category, "hämster")
     assert activity.start_time is None
     assert activity.end_time is None
     assert not activity.description
Exemplo n.º 6
0
 def localized_fact(self):
     """Make sure fact has the correct start_time."""
     fact = Fact.parse(self.activity.get_text())
     if fact.start_time:
         fact.date = self.date
     else:
         fact.start_time = hamster_now()
     return fact
Exemplo n.º 7
0
 def test_plain_name(self):
     # plain activity name
     activity = Fact.parse("just a simple case with ütf-8")
     self.assertEqual(activity.activity, "just a simple case with ütf-8")
     assert activity.start_time is None
     assert activity.end_time is None
     assert not activity.category
     assert not activity.description
Exemplo n.º 8
0
def extract_search(text):
    fact = Fact.parse(text)
    search = fact.activity
    if fact.category:
        search += "@%s" % fact.category
    if fact.tags:
        search += " #%s" % (" #".join(fact.tags))
    return search
Exemplo n.º 9
0
 def test_full(self):
     # plain activity name
     activity = Fact.parse("1225-1325 case@cat, description #ta non-tag #tag #bäg")
     self.assertEqual(activity.start_time.strftime("%H:%M"), "12:25")
     self.assertEqual(activity.end_time.strftime("%H:%M"), "13:25")
     self.assertEqual(activity.activity, "case")
     self.assertEqual(activity.category, "cat")
     self.assertEqual(activity.description, "description #ta non-tag")
     self.assertEqual(set(activity.tags), set(["bäg", "tag"]))
Exemplo n.º 10
0
 def test_tags(self):
     # plain activity name
     activity = Fact.parse("case, with added #de description #and, #some #tägs")
     self.assertEqual(activity.activity, "case")
     self.assertEqual(activity.description, "with added #de description")
     self.assertEqual(set(activity.tags), set(["and", "some", "tägs"]))
     assert not activity.category
     assert activity.start_time is None
     assert activity.end_time is None
Exemplo n.º 11
0
 def test_description(self):
     # plain activity name
     activity = Fact.parse("case, with added descriptiön")
     self.assertEqual(activity.activity, "case")
     self.assertEqual(activity.description, "with added descriptiön")
     assert not activity.category
     assert activity.start_time is None
     assert activity.end_time is None
     assert not activity.category
Exemplo n.º 12
0
    def test_with_start_and_end_time(self):
        # with time
        activity = Fact.parse("12:35-14:25 with start-end time")
        self.assertEqual(activity.activity, "with start-end time")
        self.assertEqual(activity.start_time.strftime("%H:%M"), "12:35")
        self.assertEqual(activity.end_time.strftime("%H:%M"), "14:25")

        #rest must be empty
        assert not activity.category
        assert not activity.description
Exemplo n.º 13
0
    def complete_first(self):
        text = self.get_text()
        fact = Fact.parse(text)
        search = extract_search(text)
        if not self.complete_tree.rows or not fact.activity:
            return text, None

        label = self.complete_tree.rows[0].data
        if label.startswith(search):
            return text, label[len(search):]

        return text, None
Exemplo n.º 14
0
 def on_cmdline_changed(self, widget):
     if self.master_is_cmdline:
         fact = Fact.parse(self.cmdline.get_text(), date=self.date)
         previous_cmdline_fact = self.cmdline_fact
         # copy the entered fact before any modification
         self.cmdline_fact = fact.copy()
         if fact.start_time is None:
             fact.start_time = hamster_now()
         if fact.description == previous_cmdline_fact.description:
             # no change to description here, keep the main one
             fact.description = self.fact.description
         self.fact = fact
         self.update_fields()
Exemplo n.º 15
0
 def test_copy(self):
     fact1 = Fact.parse("12:25-13:25 case@cat, description #tag #bäg")
     fact2 = fact1.copy()
     self.assertEqual(fact1.start_time, fact2.start_time)
     self.assertEqual(fact1.end_time, fact2.end_time)
     self.assertEqual(fact1.activity, fact2.activity)
     self.assertEqual(fact1.category, fact2.category)
     self.assertEqual(fact1.description, fact2.description)
     self.assertEqual(fact1.tags, fact2.tags)
     fact3 = fact1.copy(activity="changed")
     self.assertEqual(fact3.activity, "changed")
     fact3 = fact1.copy(category="changed")
     self.assertEqual(fact3.category, "changed")
     fact3 = fact1.copy(description="changed")
     self.assertEqual(fact3.description, "changed")
     fact3 = fact1.copy(tags=["changed"])
     self.assertEqual(fact3.tags, ["changed"])
Exemplo n.º 16
0
 def update_fact(self,
                 fact_id,
                 fact,
                 start_time=None,
                 end_time=None,
                 temporary=False):
     self.start_transaction()
     self.__remove_fact(fact_id)
     # to be removed once update facts use Fact directly.
     if isinstance(fact, str):
         fact = Fact.parse(fact)
         fact = fact.copy(start_time=start_time, end_time=end_time)
     result = self.__add_fact(fact, temporary)
     if not result:
         logger.warning("failed to update fact {} ({})".format(
             fact_id, fact))
     self.end_transaction()
     if result:
         self.facts_changed()
     return result
Exemplo n.º 17
0
    def update_suggestions(self, text=""):
        """
            * from previous activity | set time | minutes ago | start now
            * to ongoing | set time

            * activity
            * [@category]
            * #tags, #tags, #tags

            * we will leave description for later

            all our magic is space separated, strictly, start-end can be just dash

            phases:

            [start_time] | [-end_time] | activity | [@category] | [#tag]
        """

        res = []

        fact = Fact.parse(text)
        now = stuff.hamster_now()

        # figure out what we are looking for
        # time -> activity[@category] -> tags -> description
        # presence of an attribute means that we are not looking for the previous one
        # we still might be looking for the current one though
        looking_for = "start_time"
        fields = [
            "start_time", "end_time", "activity", "category", "tags",
            "description", "done"
        ]
        for field in reversed(fields):
            if getattr(fact, field, None):
                looking_for = field
                if text[-1] == " ":
                    looking_for = fields[fields.index(field) + 1]
                break

        fragments = [f for f in re.split("[\s|#]", text)]
        current_fragment = fragments[-1] if fragments else ""

        search = extract_search(text)

        matches = []
        for match, score in self.suggestions:
            if search in match:
                if match.startswith(search):
                    score += 10**8  # boost beginnings
                matches.append((match, score))

        # need to limit these guys, sorry
        matches = sorted(matches, key=lambda x: x[1], reverse=True)[:7]

        for match, score in matches:
            label = (fact.start_time or now).strftime("%H:%M")
            if fact.end_time:
                label += fact.end_time.strftime("-%H:%M")

            markup_label = label + " " + (stuff.escape_pango(match).replace(
                search, "<b>%s</b>" % search) if search else match)
            label += " " + match

            res.append(DataRow(markup_label, match, label))

        # list of tuples (description, variant)
        variants = []

        if self.original_fact:
            # editing an existing fact

            variant_fact = None
            if self.original_fact.end_time is None:
                description = "stop now"
                variant_fact = self.original_fact.copy()
                variant_fact.end_time = now
            elif self.original_fact == self.todays_facts[-1]:
                # that one is too dangerous, except for the last entry
                description = "keep up"
                # Do not use Fact(..., end_time=None): it would be a no-op
                variant_fact = self.original_fact.copy()
                variant_fact.end_time = None

            if variant_fact:
                variant_fact.description = None
                variant = variant_fact.serialized(prepend_date=False)
                variants.append((description, variant))

        else:
            # brand new fact
            description = "start now"
            variant = now.strftime("%H:%M ")
            variants.append((description, variant))

            prev_fact = self.todays_facts[-1] if self.todays_facts else None
            if prev_fact and prev_fact.end_time:
                since = stuff.format_duration(now - prev_fact.end_time)
                description = "from previous activity, %s ago" % since
                variant = prev_fact.end_time.strftime("%H:%M ")
                variants.append((description, variant))

            description = "start activity -n minutes ago (1 or 3 digits allowed)"
            variant = "-"
            variants.append((description, variant))

        text = text.strip()
        if text:
            description = "clear"
            variant = ""
            variants.append((description, variant))

        for (description, variant) in variants:
            res.append(DataRow(variant, description=description))

        self.complete_tree.set_rows(res)
Exemplo n.º 18
0
    def __add_fact(self,
                   serialized_fact,
                   start_time,
                   end_time=None,
                   temporary=False):
        fact = Fact.parse(serialized_fact)
        fact.start_time = start_time
        fact.end_time = end_time

        logger.info("adding fact {}".format(fact))

        start_time = start_time or fact.start_time
        end_time = end_time or fact.end_time

        if not fact.activity or start_time is None:  # sanity check
            return 0

        # get tags from database - this will create any missing tags too
        tags = [(tag['id'], tag['name'], tag['autocomplete'])
                for tag in self.get_tag_ids(fact.tags)]

        # now check if maybe there is also a category
        category_id = None
        if fact.category:
            category_id = self.__get_category_id(fact.category)
            if not category_id:
                category_id = self.__add_category(fact.category)

        # try to find activity, resurrect if not temporary
        activity_id = self.__get_activity_by_name(fact.activity,
                                                  category_id,
                                                  resurrect=not temporary)
        if not activity_id:
            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.timedelta(days=-1) <= hamster_now() - start_time <=
                dt.timedelta(days=1)):
            # pull in previous facts
            facts = self.__get_todays_facts()

            previous = None
            if facts and facts[-1]["end_time"] == None:
                previous = facts[-1]

            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 set(previous["tags"]) == set([tag[1] for tag in tags]) \
                   and (previous["description"] or "") == (fact.description or ""):
                    return None

                # if no description is added
                # see if maybe previous was too short to qualify as an activity
                if not previous["description"] \
                   and 60 >= (start_time - previous['start_time']).seconds >= 0:
                    self.__remove_fact(previous['id'])

                    # now that we removed the previous one, see if maybe the one
                    # before that is actually same as the one we want to start
                    # (glueing)
                    if len(facts) > 1 and 60 >= (
                            start_time - facts[-2]['end_time']).seconds >= 0:
                        before = facts[-2]
                        if before["activity_id"] == activity_id \
                           and set(before["tags"]) == set([tag[1] for tag in tags]):
                            # resume and return
                            update = """
                                       UPDATE facts
                                          SET end_time = null
                                        WHERE id = ?
                            """
                            self.execute(update, (before["id"], ))

                            return before["id"]
                else:
                    # otherwise stop
                    update = """
                               UPDATE facts
                                  SET end_time = ?
                                WHERE id = ?
                    """
                    self.execute(update, (start_time, previous["id"]))

        # done with the current activity, now we can solve overlaps
        if not end_time:
            end_time = self.__squeeze_in(start_time)
        else:
            self.__solve_overlaps(start_time, end_time)

        # finally add the new entry
        insert = """
                    INSERT INTO facts (activity_id, start_time, end_time, description)
                               VALUES (?, ?, ?, ?)
        """
        self.execute(insert,
                     (activity_id, start_time, end_time, fact.description))

        fact_id = self.__last_insert_rowid()

        #now link tags
        insert = ["insert into fact_tags(fact_id, tag_id) values(?, ?)"
                  ] * len(tags)
        params = [(fact_id, tag[0]) for tag in tags]
        self.execute(insert, params)

        self.__remove_index([fact_id])
        logger.info("fact successfully added, with id #{}".format(fact_id))
        return fact_id