示例#1
0
    def test_round_trip(self):
        fact = Fact.parse(
            "11:00 12:00 activity, with comma@category,, description, with comma #and #tags"
        )
        dbus_fact = to_dbus_fact_json(fact)
        return_fact = from_dbus_fact_json(dbus_fact)
        self.assertEqual(return_fact, fact)

        dbus_fact = to_dbus_fact(fact)
        return_fact = from_dbus_fact(dbus_fact)
        self.assertEqual(return_fact, fact)

        fact = Fact.parse("11:00 activity")
        dbus_fact = to_dbus_fact_json(fact)
        return_fact = from_dbus_fact_json(dbus_fact)
        self.assertEqual(return_fact, fact)

        dbus_fact = to_dbus_fact(fact)
        return_fact = from_dbus_fact(dbus_fact)
        self.assertEqual(return_fact, fact)

        range, __ = dt.Range.parse("2020-01-19 11:00 - 2020-01-19 12:00")
        dbus_range = to_dbus_range(range)
        return_range = from_dbus_range(dbus_range)
        self.assertEqual(return_range, range)
示例#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")
示例#3
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())
     # empty fact
     fact3 = Fact()
     self.assertEqual(fact3.serialized(), "")
示例#4
0
    def AddFact(self, fact_str, start_time, end_time, temporary=False):
        """Add fact specified by a string.

        Args:
            fact_str (str): string to be parsed.
            start_time (int): Start datetime timestamp.
                              For backward compatibility with the
                              gnome shell extension,
                              0 is special and means hamster_now().
                              Otherwise, overrides the parsed value.
                              -1 means None.
            end_time (int): Start datetime timestamp.
                            If different from 0, overrides the parsed value.
                            -1 means None.
        Returns:
            fact id (int), or 0 in case of failure.

        Note: see datetime.utcfromtimestamp documentation
              for the precise meaning of timestamps.
        """
        fact = Fact.parse(fact_str)

        if start_time == -1:
            fact.start_time = None
        elif start_time == 0:
            fact.start_time = stuff.hamster_now()
        else:
            fact.start_time = dt.datetime.utcfromtimestamp(start_time)

        if end_time == -1:
            fact.end_time = None
        elif end_time != 0:
            fact.end_time = dt.datetime.utcfromtimestamp(end_time)

        return self.add_fact(fact) or 0
示例#5
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)
示例#6
0
    def validate_fields(self):
        """Check fields information.

        Update gui status about entry and description validity.
        Try to merge date, activity and description informations.

        Return the consolidated fact if successful, or None.
        """
        fact = self.fact

        now = dt.datetime.now()
        self.get_widget("button-next-day").set_sensitive(self.date < now.date())

        if self.date == now.date():
            default_dt = now
        else:
            default_dt = dt.datetime.combine(self.date, now.time())

        self.draw_preview(fact.start_time or default_dt,
                          fact.end_time or default_dt)

        try:
            runtime.storage.check_fact(fact, default_day=self.date)
        except FactError as error:
            self.update_status(status="wrong", markup=str(error))
            return None

        roundtrip_fact = Fact.parse(fact.serialized(), default_day=self.date)
        if roundtrip_fact != fact:
            self.update_status(status="wrong", markup="Fact could not be parsed back")
            return None

        # nothing unusual
        self.update_status(status="looks good", markup="")
        return fact
示例#7
0
 def test_roundtrips(self):
     for start_time in (
             None,
             dt.time(12, 33),
     ):
         for end_time in (
                 None,
                 dt.time(13, 34),
         ):
             for activity in (
                     "activity",
                     "#123 with two #hash",
                     "activity, with comma",
             ):
                 for category in (
                         "",
                         "category",
                 ):
                     for description in (
                             "",
                             "description",
                             "with #hash",
                             "with, comma",
                             "with @at",
                     ):
                         for tags in (
                             [],
                             ["single"],
                             ["with space"],
                             ["two", "tags"],
                             ["with @at"],
                         ):
                             start = hamsterday_time_to_datetime(
                                 hamster_today(),
                                 start_time) if start_time else None
                             end = hamsterday_time_to_datetime(
                                 hamster_today(),
                                 end_time) if end_time else None
                             if end and not start:
                                 # end without start is not parseable
                                 continue
                             fact = Fact(start_time=start,
                                         end_time=end,
                                         activity=activity,
                                         category=category,
                                         description=description,
                                         tags=tags)
                             for range_pos in ("head", "tail"):
                                 fact_str = fact.serialized(
                                     range_pos=range_pos)
                                 parsed = Fact.parse(fact_str,
                                                     range_pos=range_pos)
                                 self.assertEqual(fact, parsed)
                                 self.assertEqual(parsed.activity,
                                                  fact.activity)
                                 self.assertEqual(parsed.category,
                                                  fact.category)
                                 self.assertEqual(parsed.description,
                                                  fact.description)
                                 self.assertEqual(parsed.tags, fact.tags)
示例#8
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

        # better fail before opening the transaction
        self.check_fact(fact)
        self.start_transaction()
        result = self.__add_fact(fact, temporary)
        self.end_transaction()

        if result:
            self.facts_changed()
        return result
示例#9
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)
     fact2 = fact1.copy()
     fact2.range.start = fact1.range.start + dt.timedelta(minutes=1)
     self.assertNotEqual(fact1, fact2)
     fact2 = fact1.copy()
     fact2.range.end = fact1.range.end + dt.timedelta(minutes=1)
     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)
示例#10
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
示例#11
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
示例#12
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
示例#13
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
示例#14
0
 def test_tags(self):
     # plain activity name
     activity = Fact.parse(
         "#case,, description with #hash,, #and, #some #tägs")
     self.assertEqual(activity.activity, "#case")
     self.assertEqual(activity.description, "description with #hash")
     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
示例#15
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
示例#16
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"]))
示例#17
0
    def start(self, *args):
        '''Start a new activity.'''
        if not args:
            print("Error: please specify activity")
            return

        fact = Fact.parse(" ".join(args), range_pos="tail")
        if fact.start_time is None:
            fact.start_time = dt.datetime.now()
        self.storage.check_fact(fact, default_day=dt.hday.today())
        self.storage.add_fact(fact)
示例#18
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
示例#19
0
 def test_commas(self):
     fact = Fact.parse(
         "11:00 12:00 activity, with comma@category,, description, with comma"
     )
     self.assertEqual(fact.activity, "activity, with comma")
     self.assertEqual(fact.category, "category")
     self.assertEqual(fact.description, "description, with comma")
     self.assertEqual(fact.tags, [])
     fact = Fact.parse(
         "11:00 12:00 activity, with comma@category,, description, with comma, #tag1, #tag2"
     )
     self.assertEqual(fact.activity, "activity, with comma")
     self.assertEqual(fact.category, "category")
     self.assertEqual(fact.description, "description, with comma")
     self.assertEqual(fact.tags, ["tag1", "tag2"])
     fact = Fact.parse(
         "11:00 12:00 activity, with comma@category,, description, with comma and #hash,, #tag1, #tag2"
     )
     self.assertEqual(fact.activity, "activity, with comma")
     self.assertEqual(fact.category, "category")
     self.assertEqual(fact.description, "description, with comma and #hash")
     self.assertEqual(fact.tags, ["tag1", "tag2"])
示例#20
0
 def on_cmdline_changed(self, widget):
     if self.master_is_cmdline:
         fact = Fact.parse(self.cmdline.get_text(), default_day=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 = dt.datetime.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()
示例#21
0
 def update_fact(self, fact_id, fact, start_time=None, end_time=None, temporary=False):
     # better fail before opening the transaction
     self.check_fact(fact)
     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
示例#22
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"])
示例#23
0
    def AddFact(self, fact_str, start_time, end_time, temporary):
        """Add fact specified by a string.

        If the parsed fact has no start, then now is used.
        To fully use the hamster fact parser, as on the cmdline,
        just pass 0 for start_time and end_time.

        Args:
            fact_str (str): string to be parsed.
            start_time (int): Start datetime ovveride timestamp (ignored if 0).
                              -1 means None.
            end_time (int): datetime ovveride timestamp (ignored if 0).
                            -1 means None.
            #temporary (boolean): historical mystery, ignored, but needed to
                                 keep the method signature stable.
                                 Do not forget to pass something (e.g. False)!
        Returns:
            fact id (int), 0 means failure.

        Note: see datetime.utcfromtimestamp documentation
              for the precise meaning of timestamps.
        """
        fact = Fact.parse(fact_str)

        # default value if none found
        if not fact.start_time:
            fact.start_time = dt.datetime.now()

        if start_time == -1:
            fact.start_time = None
        elif start_time != 0:
            fact.start_time = dt.datetime.utcfromtimestamp(start_time)

        if end_time == -1:
            fact.end_time = None
        elif end_time != 0:
            fact.end_time = dt.datetime.utcfromtimestamp(end_time)

        return self.add_fact(fact)
示例#24
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.todays_facts and 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(default_day=self.default_day)
                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)
示例#25
0
 def test_only_range(self):
     fact = Fact.parse("-20")
     assert not fact.activity
     fact = Fact.parse("-20 -10")
     assert not fact.activity