def test_sanity(): log.title(f"test_sanity()") work = default_work(TEST_START_ARROW) work.stop() with temp_sheet("/tmp/timefred-sheet-test_log__test_sanity.toml"): store.dump(work) log_action()
def edit(): if "EDITOR" not in os.environ: raise NoEditor("Please set the 'EDITOR' environment variable") data = store.load() yml = yaml.safe_dump(data, default_flow_style=False, allow_unicode=True) cmd = os.getenv('EDITOR') fd, temp_path = tempfile.mkstemp(prefix='timefred.') with open(temp_path, "r+") as f: f.write(yml.replace('\n- ', '\n\n- ')) f.seek(0) subprocess.check_call(cmd + ' ' + temp_path, shell=True) yml = f.read() f.truncate() os.close(fd) os.remove(temp_path) try: data = yaml.load(yml) except: raise InvalidYAML("Oops, that YAML doesn't appear to be valid!") store.dump(data)
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_load_store(self, work=None): log.title(f"test_load_store({work = })") if not work: work = default_work() with temp_sheet( "/tmp/timefred-sheet-test_on_device_validation_08_30.toml" ): store.dump(work) work = store.load() self.test_sanity(work=work)
def test_dump(self): work = default_work() log.title(f"test_dump({work = })") # os.environ['TIMEFRED_SHEET'] = "/tmp/timefred-sheet-test_on_device_validation_08_30.toml" # config.sheet.path = "/tmp/timefred-sheet-test_on_device_validation_08_30.toml" # store.path = "/tmp/timefred-sheet-test_on_device_validation_08_30.toml" with temp_sheet( "/tmp/timefred-sheet-test_on_device_validation_08_30.toml"): store.dump(work) work = store.load()
def tag(_tag, time="now") -> bool: time = XArrow.from_human(time) # time = human2arrow(time) if time > time.now(): raise BadTime(f"in the future: {time}") work = store.load() idx = -1 item = Entry(**work[idx]) if time < item.start: # Tag something in the past idx = -1 * next(i for i, work in enumerate(reversed(work), 1) if Entry(**work).start <= time) item_in_range = Entry(**work[idx]) if not confirm( f'{item.name_colored} started only at {c.time(item.start.strftime("%X"))}, ' f'Tag {item_in_range.name_colored} (started at {c.time(item_in_range.start.strftime("%X"))})?' ): return False item = item_in_range tag_colored = c.tag(_tag) if any( util.normalize_str(_tag) == t for t in map(util.normalize_str, item.tags)): print(f'{item.name_colored} already has tag {tag_colored}.') return False item.tags.add(_tag) work[idx]['tags'] = item.tags ok = store.dump(work) if ok: print(f"Okay, tagged {item.name_colored} with {tag_colored}.") else: print(f"Failed writing to sheet") return ok
def on(name: str, time: Union[str, XArrow], tag=None, note=None): work = store.load() if work: day = work[time.DDMMYY] activity = day[name] if activity.ongoing() and activity.has_similar_name(name): print( f'{c.orange("Already")} working on {activity.name.colored} since {c.time(activity.start.DDMMYYHHmmss)} ;)' ) return True ok = stop(time) if ok: return on(name, time, tag) breakpoint() return False entry = Entry(start=time) # assert entry # assert entry.start # assert isinstance(entry.start, XArrow) activity = Activity(name=name) # assert not activity # assert len(activity) == 0 # assert activity.name == 'Got to office', f"activity.name is not 'Got to office' but rather {activity.name!r}" # assert isinstance(activity.name, Colored), f'Not Colored, but rather {type(activity.name)}' activity.append(entry) # assert len(activity) == 1 if tag: entry.tags.add(tag) if note: note = Note(note, time) entry.notes.append(note) # work[entry.start.DDMMYY].append({str(activity.name): activity.dict(exclude=('timespan', 'name'))}) day = work[entry.start.DDMMYY] day[str(activity.name)] = activity # work[activity.start.DDMMYY].append(activity) # work.append(activity.dict()) ok = store.dump(work) if not ok: breakpoint() # message = f'{c.green("Started")} working on {activity.name_colored} at {c.time(reformat(time, timeutils.FORMATS.date_time))}' message = f'{c.green("Started")} working on {activity.name.colored} at {c.time(entry.start.DDMMYYHHmmss)}' if tag: message += f". Tag: {c.tag(tag)}" if note: message += f". Note: {note.pretty()}" print(message)
def stop(end: XArrow, tag: Tag = None, note: Note = None) -> bool: # ensure_working() work: Work = store.load() ongoing_activity: Activity = work.ongoing_activity() entry: Entry = ongoing_activity.stop(end, tag, note) if entry.start.day < end.day: if not util.confirm(f'{ongoing_activity.name} started on {c.time(entry.start.DDMMYYHHmmss)}, continue?'): # TODO: activity is wrongly stopped now, should revert or prevent return False ok = store.dump(work) print(f'{c.yellow("Stopped")} working on {ongoing_activity.name.colored} at {c.time(entry.end.DDMMYYHHmmss)}. ok: {ok}') return ok