Example #1
0
 def date(self, value):
     if self.start_time:
         self.start_time = hamsterday_time_to_datetime(
             value, self.start_time.time())
     if self.end_time:
         self.end_time = hamsterday_time_to_datetime(
             value, self.end_time.time())
Example #2
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)
Example #3
0
 def date(self, value):
     if self.start_time:
         previous_start_time = self.start_time
         self.start_time = hamsterday_time_to_datetime(value, self.start_time.time())
         if self.end_time:
             # start_time date prevails.
             # Shift end_time to preserve the fact duration.
             self.end_time += self.start_time - previous_start_time
     elif self.end_time:
         self.end_time = hamsterday_time_to_datetime(value, self.end_time.time())
Example #4
0
 def test_hamsterday_time_to_datetime(self):
     hamsterday = dt.date(2018, 8, 13)
     time = dt.time(23, 10)
     expected = dt.datetime(2018, 8, 13, 23, 10)  # 2018-08-13 23:10
     self.assertEqual(hamsterday_time_to_datetime(hamsterday, time),
                      expected)
     hamsterday = dt.date(2018, 8, 13)
     time = dt.time(0, 10)
     expected = dt.datetime(2018, 8, 14, 0, 10)  # 2018-08-14 00:10
     self.assertEqual(hamsterday_time_to_datetime(hamsterday, time),
                      expected)
Example #5
0
 def on_start_time_changed(self, widget):
     if not self.master_is_cmdline:
         # note: resist the temptation to preserve duration here;
         # for instance, end time might be at the beginning of next fact.
         new_time = self.start_time.time
         if new_time:
             if self.fact.start_time:
                 new_start_time = dt.datetime.combine(
                     self.fact.start_time.date(), new_time)
             else:
                 # date not specified; result must fall in current hamster_day
                 new_start_time = hamsterday_time_to_datetime(
                     hamster_today(), new_time)
         else:
             new_start_time = None
         self.fact.start_time = new_start_time
         # let start_date extract date or handle None
         self.start_date.date = new_start_time
         self.validate_fields()
         self.update_cmdline()
Example #6
0
def _extract_datetime(match, d="date", h="hour", m="minute", r="relative", default_day=None):
    """extract datetime from a dt_pattern match.

    h (str): name of the group containing the hour
    m (str): name of the group containing the minute
    r (str): name of the group containing the relative time
    default_day (dt.date): the datetime will belong to this hamster day if
                           date is missing.
    """
    time = _extract_time(match, h, m)
    if time:
        date_str = match.group(d)
        if date_str:
            date = parse_date(date_str)
            return dt.datetime.combine(date, time)
        else:
            return hamsterday_time_to_datetime(default_day, time)
    else:
        relative_str = match.group(r)
        if relative_str:
            return dt.timedelta(minutes=int(relative_str))
        else:
            return None
Example #7
0
def parse_fact(text, phase=None, res=None, date=None):
    """tries to extract fact fields from the string
        the optional arguments in the syntax makes us actually try parsing
        values and fallback to next phase
        start -> [end] -> activity[@category] -> tags

        Returns dict for the fact and achieved phase

        TODO - While we are now bit cooler and going recursively, this code
        still looks rather awfully spaghetterian. What is the real solution?

        Tentative syntax:
        [date] start_time[-end_time] activity[@category][, description]{[,] { })#tag}
        According to the legacy tests, # were allowed in the description
    """
    now = dt.datetime.now()

    # determine what we can look for
    phases = [
        "date",  # hamster day
        "start_time",
        "end_time",
        "tags",
        "activity",
        "category",
    ]

    phase = phase or phases[0]
    phases = phases[phases.index(phase):]
    if res is None:
        res = {}

    text = text.strip()
    if not text:
        return res

    fragment = re.split("[\s|#]", text, 1)[0].strip()

    # remove a fragment assumed to be at the beginning of text
    remove_fragment = lambda text, fragment: text[len(fragment):]

    if "date" in phases:
        # if there is any date given, it must be at the front
        try:
            date = dt.datetime.strptime(fragment, DATE_FMT).date()
            remaining_text = remove_fragment(text, fragment)
        except ValueError:
            date = now.date()
            remaining_text = text
        return parse_fact(remaining_text, "start_time", res, date)

    if "start_time" in phases or "end_time" in phases:

        # -delta ?
        delta_re = re.compile("^-[0-9]{1,3}$")
        if delta_re.match(fragment):
            # TODO untested
            # delta_re was probably thought to be used
            # alone or together with a start_time
            # but using "now" prevents the latter
            res[phase] = now + dt.timedelta(minutes=int(fragment))
            remaining_text = remove_fragment(text, fragment)
            return parse_fact(remaining_text, phases[phases.index(phase) + 1],
                              res, date)

        # only starting time ?
        m = re.search(time_re, fragment)
        if m:
            time = extract_time(m)
            res[phase] = hamsterday_time_to_datetime(date, time)
            remaining_text = remove_fragment(text, fragment)
            return parse_fact(remaining_text, phases[phases.index(phase) + 1],
                              res, date)

        # start-end ?
        start, __, end = fragment.partition("-")
        m_start = re.search(time_re, start)
        m_end = re.search(time_re, end)
        if m_start and m_end:
            start_time = extract_time(m_start)
            end_time = extract_time(m_end)
            res["start_time"] = hamsterday_time_to_datetime(date, start_time)
            res["end_time"] = hamsterday_time_to_datetime(date, end_time)
            remaining_text = remove_fragment(text, fragment)
            return parse_fact(remaining_text, "tags", res, date)

    if "tags" in phases:
        # Need to start from the end, because
        # the description can hold some '#' characters
        tags = []
        remaining_text = text
        while True:
            m = re.search(tag_re, remaining_text)
            if not m:
                break
            tag = m.group(1)
            tags.append(tag)
            # strip the matched string (including #)
            remaining_text = remaining_text[:m.start()]
        res["tags"] = tags
        return parse_fact(remaining_text, "activity", res, date)

    if "activity" in phases:
        activity = re.split("[@|#|,]", text, 1)[0]
        if looks_like_time(activity):
            # want meaningful activities
            return res

        res["activity"] = activity
        remaining_text = remove_fragment(text, activity)
        return parse_fact(remaining_text, "category", res, date)

    if "category" in phases:
        category, _, description = text.partition(",")
        res["category"] = category.lstrip("@").strip() or None
        res["description"] = description.strip() or None
        return res

    return {}