Example #1
0
File: ti.py Project: giladbarnea/ti
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='ti.')
	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)
Example #2
0
File: ti.py Project: giladbarnea/ti
def fin(time: Union[str, Arrow], back_from_interrupt=True) -> bool:
	ensure_working()

	data = store.load()

	current = data['work'][-1]
	item = Item(**data['work'][-1])

	end: XArrow = formatted2arrow(time)
	if item.start > end:
		print(f'{c.orange("Cannot")} finish {item.name_colored} at {c.time(end.HHmmss)} because it only started at {c.time(item.start.HHmmss)}.')
		return False
	if item.start.day < end.day:
		print(end)
		if not confirm(f'{item.name_colored} started on {c.time(item.start.MMDDYYHHmmss)}, continue?'):
			return False
	current['end'] = time
	item.end = time
	ok = store.dump(data)
	print(f'{c.yellow("Stopped")} working on {item.name_colored} at {c.time(item.end.HHmmss)}')
	if not ok:
		return False
	if back_from_interrupt and len(data['interrupt_stack']) > 0:
		name = data['interrupt_stack'].pop()['name']
		store.dump(data)
		on(name, time)
		if len(data['interrupt_stack']) > 0:
			print('You are now %d deep in interrupts.'
				  % len(data['interrupt_stack']))
		else:
			print("Congrats, you're out of interrupts!")
	return True
Example #3
0
File: ti.py Project: giladbarnea/ti
def tag(_tag, time="now"):
	time = human2arrow(time)
	if time > now():
		raise BadTime(f"in the future: {time}")
	data = store.load()
	idx = -1
	item = Item(**data['work'][idx])
	if time < item.start:
		# Tag something in the past
		idx = -1 * next(i for i, work in enumerate(reversed(data['work']), 1) if Item(**work).start <= time)
		item_in_range = Item(**data['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
		item = item_in_range
	tag_colored = c.b(c.tag(_tag))
	if _tag.lower() in [t.lower() for t in item.tags]:
		print(f'{item.name_colored} already has tag {tag_colored}.')
		return
	item.tags.append(_tag)
	data['work'][idx]['tags'] = item.tags

	store.dump(data)

	print(f"Okay, tagged {item.name_colored} with {tag_colored}.")
Example #4
0
File: ti.py Project: giladbarnea/ti
def on(name, time="now", _tag=None, _note=None):
	data = store.load()
	work = data['work']

	if work and 'end' not in (current := work[-1]):
		# Finish current, then start (recursively)
		current_name__lower = current["name"].lower()
		name_lower = name.lower()
		if current_name__lower == name_lower:
			print(f'{c.orange("Already")} working on {c.task(current["name"])} since {c.b(c.time(reformat(current["start"], "HH:mm:ss")))} ;)')
			return True
		ok = fin(time)
		if ok:
			return on(name, time, _tag)
		return False
Example #5
0
File: ti.py Project: giladbarnea/ti
def interrupt(name, time):
	ensure_working()

	fin(time, back_from_interrupt=False)

	data = store.load()
	if 'interrupt_stack' not in data:
		data['interrupt_stack'] = []
	interrupt_stack = data['interrupt_stack']

	interrupted = data['work'][-1]
	interrupt_stack.append(interrupted)
	store.dump(data)

	on('interrupt: ' + c.green(name), time)
	print('You are now %d deep in interrupts.' % len(interrupt_stack))
Example #6
0
File: ti.py Project: giladbarnea/ti
def status(show_notes=False):
	ensure_working()

	data = store.load()
	current = data['work'][-1]

	start_time = formatted2arrow(current['start'])
	diff = timegap(start_time, now())

	notes = current.get('notes')
	if not show_notes or not notes:
		print(f'You have been working on {c.task(current["name"])} for {c.b(c.time(diff))}.')
		return

	rprint('\n    '.join([f'You have been working on {c.task(current["name"])} for {c.b(c.time(diff))}.\nNotes:[rgb(170,170,170)]',
						  *[f'[rgb(100,100,100)]o[/rgb(100,100,100)] {n}' for n in notes],
						  '[/]']))
Example #7
0
File: ti.py Project: giladbarnea/ti
def note(content, time="now"):
	# ensure_working()
	time = human2arrow(time)
	if time > now():
		raise BadTime(f"in the future: {time}")
	breakpoint()
	content_and_time = content.strip() + f' ({time.HHmmss})'
	data = store.load()
	idx = -1
	item = Item(**data['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: Item(**w), reversed(data['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

	# refactor this out when Note class
	content_lower = content.lower()
	for n in item.notes:
		if n.lower().startswith(content_lower):
			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)
	data['work'][idx]['notes'] = item.notes
	store.dump(data)

	print(f'Noted {c.b(c.note(content_and_time))} to {item.name_colored}')
Example #8
0
File: ti.py Project: giladbarnea/ti
def is_working():
	data = store.load()
	return data.get('work') and 'end' not in data['work'][-1]
Example #9
0
def log(period="today", *, detailed=True, groupby: Literal['t', 'tag'] = None):
    if groupby and groupby not in ('t', 'tag'):
        raise ValueError(
            f"log({period = }, {groupby = }) groupby must be either 't' | 'tag'"
        )
    data = store.load()
    work = data['work'] + data['interrupt_stack']
    _log = defaultdict(lambda: {
        'duration': timedelta(),
        'times': [],
        'notes': []
    })
    current = None
    period_arrow = human2arrow(period)
    _now = now()

    by_tag = defaultdict(set)
    try:
        item = next(item for item in map(lambda w: Item(**w), reversed(work))
                    if item.start.full == period_arrow.full)
    except StopIteration:
        # Optimization: for loop, stop when already passed
        print(f"{c.orange('Missing')} logs for {period_arrow.full}")
        return False
    # stop = False
    # total_secs = 0
    # for i, item in enumerate(map(lambda w: Item(**w), reversed(work))):
    # 	if item.start.day != period_arrow.day:
    # 		if stop:
    # 			break
    # 		continue
    # 	if period_arrow.month != item.start.month and period_arrow.year != item.start.year:
    # 		break
    # 	if groupby and groupby in ('t', 'tag'):
    # 		if not tags:
    # 			by_tag[None].add(name)
    # 		else:
    # 			for t in tags:
    # 				by_tag[t].add(name)

    for note in item.notes:
        match = note_time_re.fullmatch(note)
        if match:
            match_groups = match.groups()
            note_content = match_groups[0]
            note_time = match_groups[1]
            _log[item.name]['notes'].append((note_time, note_content))
        else:
            _log[item.name]['notes'].append((None, note))
    # _log[item.name]['notes'].append(note)
    _log[item.name]['tags'] = item.tags

    _log[item.name]['times'].append((item.start, item.end))

    if item.end:
        _log[item.name]['duration'] += item.end - item.start
    else:
        _log[item.name]['duration'] += _now - item.start
        current = item.name

    # Get total duration and make it pretty
    name_col_len = 0
    total_secs = 0
    for name, item in _log.items():
        name_col_len = max(name_col_len, len(c.strip_color(name)), 24)

        duration = int(item['duration'].total_seconds())
        total_secs += duration
        pretty = secs2human(duration)
        _log[name]['pretty'] = pretty

    title = c.b(c.w255(period_arrow.full))
    ago = arrows2rel_time(_now, period_arrow)
    if ago:
        title += f' {c.dim("| " + arrows2rel_time(_now, period_arrow))}'
    # if len(period) > 2:
    #     title = period.title()
    # else:
    #     title = f"{period[0]} {times.ABBREVS[period[1]]} ago"
    print(title + '\n' if detailed else '')

    # rprint(f"[b bright_white]{title}'s logs:[/]" + arrows2rel_time(_now, period_arrow) + '\n' if detailed else '')
    if groupby:
        for _tag, names in by_tag.items():
            print(c.tag(_tag))
            # print(f"\x1b[38;2;204;120;50m{_tag}\x1b[39m")
            for name in names:
                print_log(name, _log[name], current, detailed, name_col_len)
        return

    for name, item in sorted(
            _log.items(),
            key=lambda entry: min(map(lambda _t: _t[0], entry[1]['times']))):
        print_log(name, item, current, detailed, name_col_len)

    rprint(f"[b bright_white]Total:[/] {secs2human(total_secs)}")