def stop(self, time: Union[str, XArrow] = None, tag: Union[str, Tag] = None, note: Union[str, Note] = None) -> Entry: """ Returns: Last entry. Raises: ValueError: if the activity is not ongoing """ last_entry = self.safe_last_entry() if not last_entry or last_entry.end: raise ValueError(f'{self.shortrepr()} is not ongoing') if not time: time = XArrow.now() if last_entry.start > time: raise ValueError( f'Cannot stop {self.shortrepr()} before start time (tried to stop at {time!r})' ) last_entry.end = time if tag: last_entry.tags.add(tag) if note: last_entry.notes.append(note) return last_entry
def on(self, name: Union[str, Activity], time: Union[str, XArrow] = None, tag: Union[str, Tag] = None, note: Union[str, Note] = None) -> Activity: """ Raises: ValueError: if an activity with the same / similar name is already ongoing """ if time is None: time = XArrow.now() try: ongoing_activity = self.ongoing_activity() except ValueError: # No ongoing activity -> start new activity day = self[time.DDMMYY] activity: Activity = day[name] activity.start(time, tag, note) return activity else: # Ongoing activity -> stop it and start new activity if name == ongoing_activity.name: raise ValueError( f'{ongoing_activity.shortrepr()} is already ongoing') if ongoing_activity.has_similar_name(name): raise ValueError( f'{ongoing_activity.shortrepr()} is ongoing, and has a similar name to {name!r}' ) ongoing_activity.stop(time) day = self[time.DDMMYY] activity: Activity = day[name] activity.start(time, tag, note) return activity
def note(content, time="now"): time = XArrow.from_human(time) # time = human2arrow(time) if time > XArrow.now(): raise BadTime(f"in the future: {time}") content_and_time = content.strip() + f' ({time.HHmmss})' work = store.load() idx = -1 item = Entry(**work[idx]) if time < item.start: # Note for something in the past idx, item_in_range = next((i, item) for i, item in enumerate(map(lambda w: Entry(**w), reversed(work)), 1) if item.start.full == time.full) idx *= -1 if item_in_range.name == item.name: item = item_in_range else: if not confirm(f'{item.name_colored} started only at {c.time(item.start.strftime("%X"))},\n' f'note to {item_in_range.name_colored} (started at {c.time(item_in_range.start.strftime("%X"))})?'): return item = item_in_range for n in item.notes: if n.is_similar(content): if not confirm(f'{item.name_colored} already has this note: {c.b(c.note(n))}.\n' 'Add anyway?'): return item.notes.append(content_and_time) work[idx]['notes'] = item.notes store.dump(work) print(f'Noted {c.b(c.note(content_and_time))} to {item.name_colored}')
def test_sub(self): minute = 60 hour = minute * 60 day = hour * 24 week = day * 7 present = XArrow.now() past = present.shift(weeks=-3, days=-3) td = present - past assert td.days == 3 * 7 + 3 secs = int(td.total_seconds()) assert secs == 3 * week + 3 * day
def test_arrows2rel_time(): present = XArrow.now() past = present.shift(weeks=-3, days=-3) assert arrows2rel_time(present, past) == '3 weeks & 3 days ago' present = XArrow.from_absolute('21/12/21') past = XArrow.from_absolute('01/12/21') secs = int((present - past).total_seconds()) assert 20 * 24 * 3600 <= secs <= 21 * 24 * 3600 ret = arrows2rel_time(present, past) assert ret == '2 weeks & 6 days ago'
def stop(self, time: Union[str, XArrow] = None, tag: Union[str, Tag] = None, note: Union[str, Note] = None) -> Optional[Activity]: """ Raises: ValueError: if there is no ongoing activity """ ongoing_activity = self.ongoing_activity() if not time: time = XArrow.now() ongoing_activity.stop(time=time, tag=tag, note=note) stopped_activity = ongoing_activity return stopped_activity
def test_dehumanize_static(self): now_dehumanized = XArrow.dehumanize("now") now = XArrow.now() assert_arrows_soft_eq(now_dehumanized, now) today = XArrow.dehumanize('today') assert_arrows_soft_eq(today, now) yesterday = XArrow.dehumanize('yesterday') now_shift_yesterday = now.shift(days=-1) assert_arrows_soft_eq(now_shift_yesterday, yesterday) tomorrow = XArrow.dehumanize('tomorrow') now_shift_tomorrow = now.shift(days=+1) assert_arrows_soft_eq(now_shift_tomorrow, tomorrow)
def default_work(day: XArrow = None) -> Work: """ Returns Work of one day with a single activity, "Got to office": [{"start": "02:20"}] """ if not day: day = XArrow.now() sheet = { day.DDMMYY: { "Got to office": [{ "start": "02:20:00" }], # "Integration": [{"start": "02:20:00", "end": "02:30:00"}] } } work = Work(Day, **sheet) return work
def test_from_human_static(self): now = XArrow.from_human('now') today = XArrow.from_human('today') assert now == today == XArrow.now() yesterday = XArrow.from_human('yesterday') assert yesterday == now.shift(days=-1) assert yesterday.day == now.day - 1 eight_days_ago = XArrow.from_human('8 days ago') assert eight_days_ago == now.shift(days=-8) assert eight_days_ago.day == now.day - 8 dec_first_21 = XArrow.from_human('01/12/21') assert dec_first_21.year == 2021 assert dec_first_21.month == 12 assert dec_first_21.day == 1 thursday = XArrow.from_human('thursday') assert thursday.strftime('%A') == 'Thursday'
def test_stop_when_not_ongoing(self): log.title(f"test_models.test_stop_when_not_ongoing()") work = default_work(TEST_START_ARROW) day: Day = work.__getitem__(TEST_START_ARROW.DDMMYY) got_to_office_activity: Activity = day.__getitem__("Got to office") assert got_to_office_activity.ongoing() is True now = XArrow.now() entry: Entry = got_to_office_activity.stop(now) assert entry is got_to_office_activity.safe_last_entry() assert entry.end == now assert got_to_office_activity.ongoing() is False with assert_raises(ValueError, f'{got_to_office_activity} is not ongoing'): got_to_office_activity.stop()
def test_from_absolute_now(self): now = XArrow.now().replace(second=0) from_absolute_now = XArrow.from_absolute(now) assert_arrows_soft_eq(from_absolute_now, now) assert from_absolute_now is now for fmt in [ # FORMATS.date, # FORMATS.short_date: ['month', 'day'], FORMATS.time, FORMATS.short_time, # FORMATS.datetime: ['year', 'month', 'day', 'hour', 'minute', 'second'], # FORMATS.shorter_datetime: ['year', 'month', 'day', 'hour', 'minute'], # FORMATS.short_datetime: ['month', 'day', 'hour', 'minute'], ]: formatted = now.format(fmt) from_absolute_formatted: XArrow = XArrow.from_absolute( formatted) assert_arrows_soft_eq(from_absolute_formatted, now)
def status(show_notes=False): ensure_working() data = store.load() current = Entry(**data[-1]) duration = Timespan(current.start, XArrow.now()).human_duration # diff = timegap(current.start, now()) # notes = current.get('notes') if not show_notes or not current.notes: print( f'You have been working on {current.name.colored} for {c.time(duration)}.' ) return print('\n '.join([ f'You have been working on {current.name_colored} for {c.time(duration)}.\nNotes:', # [rgb(170,170,170)] *[f'{c.grey100("o")} {n.pretty()}' for n in current.notes] ]))
def test_log_01_12_21(capsys): raw_data = dedent(''' ["01/12/21"] "Got to office" = "10:00" [["01/12/21"."Integration"]] synced = true start = 10:30:00 end = 11:00:00 [["01/12/21"."Integration"]] synced = true start = 11:20:00 end = 11:40:00 [["01/12/21"."Integration - Weak WIFI Password"]] synced = true start = 13:30:00 end = 13:40:00 notes = {"13:40:00" = "With Vlad, tested, done, everything as expected"} [["01/12/21"."immediate endTime quiet time default value is empty str"]] start = 14:00:00 end = 17:30:00 jira = "ASM-13925" synced = true ''') sheet_path = '/tmp/timefred-sheet--test-action--test-log--test-log-01-12-21.toml' with open(sheet_path, 'w') as sheet: sheet.write(raw_data) with temp_sheet(sheet_path): log_action('01/12/21') captured = capsys.readouterr() output = captured.out output_lines = output.splitlines() now = XArrow.now() past = XArrow.from_absolute('01/12/21') from timefred.time.timeutils import arrows2rel_time assert decolor(output_lines[0] ) == f'Wednesday, 01/12/21 | {arrows2rel_time(now, past)}'
from timefred.time import XArrow print() import os os.environ.setdefault('TIMEFRED_TESTING', '1') print(f"{os.environ['TIMEFRED_TESTING'] = }") TEST_START_ARROW: XArrow = XArrow.now()
def parse_args(argv=[]) -> tuple[Callable, dict]: if not argv: argv = sys.argv # *** log # ** timefred # timefred -> log(detailed=True) argv_len = len(argv) if argv_len == 1: return action.log, {'detailed': True} # ** timefred thursday if len(argv[1]) > 1: if argv[1].lower() == 'yesterday': return action.log, {'time': argv[1], 'detailed': True} with suppress(ValueError): isoweekday(argv[1]) return action.log, {'time': argv[1], 'detailed': True} head = argv[1] tail: list[str] = argv[2:] # ** log if head in ('l', 'l-', 'log', 'log-'): groupby = None with suppress(ValueError, AttributeError): groupby_idx = tail.index('-g') groupby = tail[groupby_idx + 1] tail = tail[:groupby_idx] if tail: time = ' '.join(tail) else: time = 'today' args = {'time': time, 'detailed': '-' not in head, 'groupby': groupby} return action.log, args # *** help if 'help' in head or head in ('-h', 'h'): print(__doc__, file=sys.stderr) sys.exit(0) # *** edit elif head in ('e', 'edit'): return action.edit, {} # *** on elif head in ('+', 'o', 'on'): if not tail: raise BadArguments("Need the name of whatever you are working on.") name = tail.pop(0) _tag = None _note = None if tail: with suppress(ValueError): _tag_idx = tail.index('-t') _tag = tail[_tag_idx + 1] tail = tail[:_tag_idx] with suppress(ValueError): _note_idx = tail.index('-n') _note = tail[_note_idx + 1] tail = tail[:_note_idx] time = XArrow.from_human(' '.join(tail) if tail else 'now') else: time = XArrow.now() args = {'name': name, 'time': time, 'tag': _tag, 'note': _note} return action.on, args # *** stop elif head in ('-', 'stop'): args = {'time': XArrow.from_human(' '.join(tail) if tail else 'now')} return action.stop, args # *** status elif head in ('?', '??', 's', 's+', 'status', 'status+'): args = {'show_notes': '+' in head} return action.status, args # *** tag elif head in ('t', 'tag'): if not tail: raise BadArguments("Please provide a tag.") if len(tail) == 2: _tag, time = tail args = {'tag': _tag, 'time': time} elif len(tail) == 1: args = {'tag': tail[0]} else: args = {'tag': ' '.join(tail)} return action.tag, args # *** note elif head in ('n', 'note'): if not tail: raise BadArguments("Please provide some text to be noted.") if len(tail) == 2: content, time = tail args = {'content': content, 'time': time} elif len(tail) == 1: args = {'content': tail[0]} else: args = {'content': ' '.join(tail)} return action.note, args # *** interrupt # elif head in ('i', 'interrupt'): # if not tail: # raise BadArguments("Need the name of whatever you are working on.") # # name = tail.pop(0) # args = { # 'name': name, # 'time': human2formatted(' '.join(tail) if tail else 'now'), # } # return interrupt, args # *** aggregate elif head == 'a' or head.startswith('ag'): if not tail: raise BadArguments("Need at least <start> <stop>") if len(tail) == 1: times = tail[0] if '-' in times: start, stop = map(str.strip, times.partition('-')) elif ' ' in times: start, stop = map(str.strip, times.partition(' ')) else: raise BadArguments("Need at least <start> <stop>") else: start, stop, *tail = tail # start_arw = human2arrow(start) # stop_arw = human2arrow(stop) start_arw = XArrow.from_human(start) stop_arw = XArrow.from_human(stop) breakpoint() # *** _dev if head == '_dev': if tail[0] == 'generate completion': from timefred._dev import generate_completion return generate_completion, {} raise BadArguments("I don't understand %r" % (head, ))
def test_dehumanize_instance(self): now = XArrow.now() now_dehumanized = now.dehumanize("now") assert_arrows_soft_eq(now_dehumanized, now) today = now.dehumanize('today') assert_arrows_soft_eq(today, now) assert_arrows_soft_eq(today, now_dehumanized) # * Past # 1 day ago yesterday = now.dehumanize('yesterday') assert_arrows_soft_eq(now.shift(days=-1), yesterday) a_day_ago = now.dehumanize('a day ago') assert_arrows_soft_eq(a_day_ago, yesterday) _1_day_ago = now.dehumanize('1 day ago') assert_arrows_soft_eq(_1_day_ago, yesterday) _1d_ago = now.dehumanize('1d ago') assert_arrows_soft_eq(_1d_ago, yesterday) _1_days_ago = now.dehumanize('1 days ago') assert_arrows_soft_eq(_1_days_ago, yesterday) _1_d_ago = now.dehumanize('1 d ago') assert_arrows_soft_eq(_1_d_ago, yesterday) a_day = now.dehumanize('a day') assert_arrows_soft_eq(a_day, yesterday) _1_day = now.dehumanize('1 day') assert_arrows_soft_eq(_1_day, yesterday) _1d = now.dehumanize('1d') assert_arrows_soft_eq(_1d, yesterday) _1_d = now.dehumanize('1 d') assert_arrows_soft_eq(_1_d, yesterday) _1_days = now.dehumanize('1 days') assert_arrows_soft_eq(_1_days, yesterday) # 5 days ago five_days_ago = now.shift(days=-5) _5_day_ago = now.dehumanize('5 day ago') assert_arrows_soft_eq(_5_day_ago, five_days_ago) _5d_ago = now.dehumanize('5d ago') assert_arrows_soft_eq(_5d_ago, five_days_ago) _5_days_ago = now.dehumanize('5 days ago') assert_arrows_soft_eq(_5_days_ago, five_days_ago) _5_d_ago = now.dehumanize('5 d ago') assert_arrows_soft_eq(_5_d_ago, five_days_ago) _5_day = now.dehumanize('5 day') assert_arrows_soft_eq(_5_day, five_days_ago) _5d = now.dehumanize('5d') assert_arrows_soft_eq(_5d, five_days_ago) _5_d = now.dehumanize('5 d') assert_arrows_soft_eq(_5_d, five_days_ago) _5_days = now.dehumanize('5 days') assert_arrows_soft_eq(_5_days, five_days_ago) # months _5_months = now.dehumanize('5 months') assert_arrows_soft_eq(_5_months, now.shift(months=-5)) _5_month = now.dehumanize('5 month') assert_arrows_soft_eq(_5_month, now.shift(months=-5)) # Complex _5h_32m_1s_ago = now.dehumanize('5h 32m 1s ago') assert_arrows_soft_eq(_5h_32m_1s_ago, now.shift(hours=-5, minutes=-32, seconds=-1)) _5_minutes_5_months = now.dehumanize('5 minutes 5 months') assert_arrows_soft_eq(_5_minutes_5_months, now.shift(minutes=-5, months=-5)) _5m_5_months = now.dehumanize('5m 5 months') assert_arrows_soft_eq(_5m_5_months, now.shift(minutes=-5, months=-5)) _5m_5M = now.dehumanize('5m 5M') assert_arrows_soft_eq(_5m_5M, now.shift(minutes=-5, months=-5)) _5m_5M_7w = now.dehumanize('5m 5M 7w') now_shift_5_minutes_5_months_7_weeks_ago = now.shift(minutes=-5, months=-5, weeks=-7) assert_arrows_soft_eq(_5m_5M_7w, now_shift_5_minutes_5_months_7_weeks_ago) for perm in permutations(['5m', '5M', '7w'], 3): complex = now.dehumanize(' '.join(perm)) assert_arrows_soft_eq( complex, now_shift_5_minutes_5_months_7_weeks_ago) # * Future in_1_days = now.dehumanize('in 1 days') tomorrow = now.dehumanize('tomorrow') assert_arrows_soft_eq(in_1_days, tomorrow) assert_arrows_soft_eq(now.shift(days=+1), tomorrow) assert_arrows_soft_eq(now.shift(days=+1), in_1_days) in_1_day = now.dehumanize('in 1 day') assert_arrows_soft_eq(in_1_day, tomorrow) in_5_days = now.dehumanize('in 5 days') assert_arrows_soft_eq(now.shift(days=+5), in_5_days) in_5_day = now.dehumanize('in 5 day') assert_arrows_soft_eq(in_5_day, now.shift(days=+5)) now_shift_in_5_minutes_5_months_7_weeks = now.shift(minutes=+5, months=+5, weeks=+7) for perm in permutations(['5m', '5M', '7w'], 3): complex = now.dehumanize('in ' + ' '.join(perm)) assert_arrows_soft_eq(complex, now_shift_in_5_minutes_5_months_7_weeks)
def test_dehumanize_vanilla(self): """Make sure we don't break vanilla Arrow.dehumanize() functionality""" log.title('TestXArrow.Test_dehumanize.test_dehumanize_vanilla') now = XArrow.now() now_dehumanized = XArrow.dehumanize("now") assert_arrows_soft_eq(now_dehumanized, now) assert_arrows_soft_eq(XArrow.dehumanize("just now"), now) # * 1 unit # "hour": "an hour", "days": "{0} days" for unit, expression in EnglishLocale.timeframes.items(): for relative_fmt in ("{0} ago", "in {0}"): if 'now' in expression: continue if '{0}' in expression: shift = randint(2, 4) hardcoded_number = expression.format( shift) # 3 seconds human_expression = relative_fmt.format( hardcoded_number) # 3 seconds ago / in 3 seconds else: shift = 1 human_expression = relative_fmt.format( expression) # a second ago / in a second dehumanized_static = XArrow.dehumanize(human_expression) if 'ago' in relative_fmt: shift *= -1 shift_kwargs = {unit.removesuffix('s') + 's': shift} now = XArrow.now() now_shifted = now.shift(**shift_kwargs) try: assert_arrows_soft_eq(dehumanized_static, now_shifted) except AssertionError: dehumanized_static = XArrow.dehumanize( human_expression) now = XArrow.now() now_shifted = now.shift(**shift_kwargs) assert_arrows_soft_eq(dehumanized_static, now_shifted) dehumanized_instance = now.dehumanize(human_expression) assert_arrows_soft_eq(dehumanized_instance, now_shifted) # * 2 units for time_unit_1 in TIME_UNITS: for time_unit_2 in TIME_UNITS: if time_unit_1 == time_unit_2: continue if random() < 0.5: continue shift_1 = randint(2, 4) shift_2 = randint(2, 4) singular_time_unit_1 = (f"a" if time_unit_1 != "hour" else "an") + f" {time_unit_1}" singular_time_unit_2 = (f"a" if time_unit_2 != "hour" else "an") + f" {time_unit_2}" plural_time_unit_1 = f"{shift_1} {time_unit_1}s" plural_time_unit_2 = f"{shift_2} {time_unit_2}s" expressions = {} for fmt in ["{0} and {1}", "{0}, {1}", "{0} {1}"]: expressions[ fmt.format(plural_time_unit_1, plural_time_unit_2) + " ago"] = (True, True) expressions["in " + fmt.format( plural_time_unit_1, plural_time_unit_2)] = (True, True) expressions[fmt.format(plural_time_unit_1, singular_time_unit_2) + " ago"] = (True, False) expressions["in " + fmt.format(plural_time_unit_1, singular_time_unit_2)] = (True, False) expressions[fmt.format(singular_time_unit_1, plural_time_unit_2) + " ago"] = (False, True) expressions["in " + fmt.format(singular_time_unit_1, plural_time_unit_2)] = (False, True) expressions[fmt.format(singular_time_unit_1, singular_time_unit_2) + " ago"] = (False, False) expressions["in " + fmt.format(singular_time_unit_1, singular_time_unit_2)] = (False, False) for human_expression, quantity_tuple in expressions.items( ): shift_kwargs = {} sign = 1 if human_expression.startswith("in ") else -1 if quantity_tuple[0]: shift_kwargs[time_unit_1 + 's'] = shift_1 * sign else: shift_kwargs[time_unit_1 + 's'] = 1 * sign if quantity_tuple[1]: shift_kwargs[time_unit_2 + 's'] = shift_2 * sign else: shift_kwargs[time_unit_2 + 's'] = 1 * sign now = XArrow.now() dehumanized_instance_vanilla = Arrow.now().dehumanize( human_expression) dehumanized_instance_vanilla.microsecond = 0 dehumanized_static = XArrow.dehumanize( human_expression) now_shifted = now.shift(**shift_kwargs) dehumanized_instance = now.dehumanize(human_expression) try: assert_arrows_soft_eq(dehumanized_instance, now_shifted) assert_arrows_soft_eq(dehumanized_static, now_shifted) assert_arrows_soft_eq( dehumanized_instance, dehumanized_instance_vanilla) assert_arrows_soft_eq( dehumanized_static, dehumanized_instance_vanilla) except AssertionError: now = XArrow.now() dehumanized_instance = now.dehumanize( human_expression) now_shifted = now.shift(**shift_kwargs) assert_arrows_soft_eq(dehumanized_instance, now_shifted) dehumanized_static = XArrow.dehumanize( human_expression) assert_arrows_soft_eq(dehumanized_static, now_shifted) dehumanized_instance_vanilla = Arrow.now( ).dehumanize(human_expression) assert_arrows_soft_eq( dehumanized_instance, dehumanized_instance_vanilla) assert_arrows_soft_eq( dehumanized_static, dehumanized_instance_vanilla) # * 3 units for time_unit_3 in TIME_UNITS: if time_unit_3 == time_unit_1 or time_unit_3 == time_unit_2: continue if random() < 0.75: continue shift_3 = randint(2, 4) singular_time_unit_3 = (f"a" if time_unit_3 != "hour" else "an") + f" {time_unit_3}" plural_time_unit_3 = f"{shift_3} {time_unit_3}s" expressions = {} for fmt in [ "{0} and {1} and {2}", "{0} and {1}, {2}", "{0} and {1} {2}", "{0}, {1}, {2}", "{0}, {1} and {2}", "{0}, {1} {2}", "{0} {1} {2}", "{0} {1}, {2}", "{0} {1} and {2}", ]: for q1, q2, q3 in product(["plural", "singular"], ["plural", "singular"], ["plural", "singular"]): past_human_expression = eval( f"fmt.format({q1}_time_unit_1, {q2}_time_unit_2, {q3}_time_unit_3) + ' ago'" ) future_human_expression = eval( f"'in ' + fmt.format({q1}_time_unit_1, {q2}_time_unit_2, {q3}_time_unit_3)" ) quantity_tuple = (q1 == "plural", q2 == "plural", q3 == "plural") expressions[ past_human_expression] = quantity_tuple expressions[ future_human_expression] = quantity_tuple for human_expression, quantity_tuple in expressions.items( ): shift_kwargs = {} sign = 1 if human_expression.startswith( "in ") else -1 if quantity_tuple[0]: shift_kwargs[time_unit_1 + 's'] = shift_1 * sign else: shift_kwargs[time_unit_1 + 's'] = 1 * sign if quantity_tuple[1]: shift_kwargs[time_unit_2 + 's'] = shift_2 * sign else: shift_kwargs[time_unit_2 + 's'] = 1 * sign if quantity_tuple[2]: shift_kwargs[time_unit_3 + 's'] = shift_3 * sign else: shift_kwargs[time_unit_3 + 's'] = 1 * sign now = XArrow.now() dehumanized_instance_vanilla = Arrow.now( ).dehumanize(human_expression) dehumanized_static = XArrow.dehumanize( human_expression) now_shifted = now.shift(**shift_kwargs) dehumanized_instance = now.dehumanize( human_expression) try: assert_arrows_soft_eq(dehumanized_instance, now_shifted) assert_arrows_soft_eq(dehumanized_static, now_shifted) assert_arrows_soft_eq( dehumanized_instance, dehumanized_instance_vanilla) assert_arrows_soft_eq( dehumanized_static, dehumanized_instance_vanilla) except AssertionError: now = XArrow.now() dehumanized_instance = now.dehumanize( human_expression) now_shifted = now.shift(**shift_kwargs) assert_arrows_soft_eq(dehumanized_instance, now_shifted) dehumanized_static = XArrow.dehumanize( human_expression) assert_arrows_soft_eq(dehumanized_static, now_shifted) dehumanized_instance_vanilla = Arrow.now( ).dehumanize(human_expression) assert_arrows_soft_eq( dehumanized_instance, dehumanized_instance_vanilla) assert_arrows_soft_eq( dehumanized_static, dehumanized_instance_vanilla)
def test_sanity(self, work=None): log.title(f"test_sanity({work = })") if not work: work = default_work(TEST_START_ARROW) log.debug('work (Work)') assert isinstance(work, Work) assert work assert len(work) == 1 assert TEST_START_ARROW.DDMMYY in work log.debug('Work["30/12/99"] -> Day') day: Day = work[TEST_START_ARROW.DDMMYY] assert isinstance(day, Day) assert day assert len(day) == 1 assert "Got to office" in day log.debug('Day["Got to office"] -> Activity (ongoing)') got_to_office_activity: Activity = day["Got to office"] assert isinstance(got_to_office_activity, Activity) assert got_to_office_activity assert len(got_to_office_activity) == 1 assert isinstance(got_to_office_activity.name, Colored) got_to_office_activity_is_ongoing = got_to_office_activity.ongoing( ) assert got_to_office_activity_is_ongoing is True assert got_to_office_activity.name == "Got to office" log.debug( 'Day["On Device Validation"] -> Activity (not ongoing)') device_validation_activity: Activity = day[ "On Device Validation"] assert got_to_office_activity.name == "Got to office" assert isinstance(device_validation_activity, Activity) assert device_validation_activity.name == "On Device Validation" assert not device_validation_activity assert len(device_validation_activity) == 0 assert isinstance(device_validation_activity.name, Colored) device_validation_activity_is_ongoing = device_validation_activity.ongoing( ) assert device_validation_activity_is_ongoing is False log.debug('Work.ongoing_activity() -> Activity') ongoing_activity: Activity = work.ongoing_activity() assert ongoing_activity assert isinstance(ongoing_activity, Activity) assert ongoing_activity.name == "Got to office" assert isinstance(ongoing_activity.name, Colored) assert len(ongoing_activity) == 1 assert ongoing_activity.ongoing() is True assert ongoing_activity != device_validation_activity assert ongoing_activity == got_to_office_activity assert ongoing_activity is ongoing_activity assert got_to_office_activity is got_to_office_activity assert ongoing_activity is got_to_office_activity assert device_validation_activity.name == "On Device Validation" assert ongoing_activity.name == "Got to office" ongoing_activity_copy = work.ongoing_activity() assert ongoing_activity_copy is ongoing_activity log.debug('Activity.stop() -> Entry') got_to_office_last_entry: Entry = ongoing_activity.stop() assert isinstance(got_to_office_last_entry, Entry) assert got_to_office_last_entry assert got_to_office_last_entry.end assert ongoing_activity.ongoing() is False log.debug('Activity.stop() -> ValueError (not ongoing)') with assert_raises( ValueError, match=f"{ongoing_activity!r} is not ongoing"): ongoing_activity.stop() log.debug( 'Work.ongoing_activity() -> ValueError (no ongoing activity)' ) with assert_raises(ValueError, match="No ongoing activity"): work.ongoing_activity() log.debug('Work.on("Something New") -> Activity') something_new_activity: Activity = work.on("Something New") assert isinstance(something_new_activity, Activity) assert something_new_activity assert len(something_new_activity) == 1 assert something_new_activity.name == "Something New" something_new_activity_is_ongoing = something_new_activity.ongoing( ) assert something_new_activity_is_ongoing is True assert device_validation_activity.name == "On Device Validation" assert ongoing_activity.name == "Got to office" assert device_validation_activity.ongoing() is False assert ongoing_activity.ongoing() is False log.debug('Activity.start() -> ValueError (already ongoing)') with assert_raises( ValueError, match=f"{something_new_activity!r} is already ongoing" ): something_new_activity.start() log.debug( 'Work.on("Something New") -> ValueError (already ongoing)') with assert_raises( ValueError, match=f"{something_new_activity!r} is already ongoing" ): work.on(something_new_activity.name) log.debug('Work.on("Something New2") -> Activity') something_new2_activity: Activity = work.on("Something New2") assert isinstance(something_new2_activity, Activity) assert something_new2_activity assert len(something_new2_activity) == 1 assert something_new2_activity.name == "Something New2" assert something_new2_activity.ongoing() is True assert device_validation_activity.name == "On Device Validation" assert ongoing_activity.name == "Got to office" assert device_validation_activity.ongoing() is False assert ongoing_activity.ongoing() is False assert something_new_activity.ongoing() is False log.debug( 'Work.on("something-new 2") -> ValueError (has similar name)' ) assert something_new2_activity.has_similar_name( "something-new 2") is True with assert_raises( ValueError, match= f"{something_new2_activity!r} is ongoing, and has a similar name to 'something-new 2" ): work.on("something-new 2") log.debug('Work.stop() -> Optional[Activity]') stop_time = XArrow.now() stopped_activity: Activity = work.stop(stop_time) assert stopped_activity.name == "Something New2" assert stopped_activity[-1].end == stop_time assert stopped_activity.ongoing() is False with assert_raises(ValueError, match=f"No ongoing activity"): work.ongoing_activity() with assert_raises(ValueError, match=f"No ongoing activity"): work.stop()