Exemple #1
0
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)
Exemple #2
0
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
Exemple #3
0
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}')
Exemple #4
0
 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)
Exemple #5
0
 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()
Exemple #6
0
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)
Exemple #7
0
        def test_subtable_activity__time_in_proper(self):
            raw_data = '["02/12/21"]\n[["02/12/21"."Got to office"]]\nstart = 09:40:00'
            sheet_path = '/tmp/timefred-sheet--test-store--test-load--test-subtable-activity--time-in-proper.toml'
            with open(sheet_path, 'w') as sheet:
                sheet.write(raw_data)

            with temp_sheet(sheet_path):
                work = store.load()
            day: Day = work['02/12/21']
            activity: Activity = day['Got to office']
            entry: Entry = activity.safe_last_entry()
            assert isinstance(entry.start, XArrow)
            assert entry.start.HHmmss == "09:40:00"
            assert entry.end is UNSET
Exemple #8
0
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
Exemple #9
0
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 generate_completion():
    work = store.load()
    tags = set([x.get('tags')[0] for x in work if x.get('tags')])
    print(f"""function completion.tf(){{
    current=${{COMP_WORDS[COMP_CWORD]}}
    prev=${{COMP_WORDS[$COMP_CWORD - 1]}}
    possible_completions=''
    case "$prev" in
        tf)
            possible_completions='l h e on f'
            ;;
    esac
    if [[ "$prev" == -t ]]; then
        possible_completions="{" ".join(tags)}"
    fi
    COMPREPLY=($(compgen -W "$possible_completions" -- "${{current}}"))
}}
complete -o default -F completion.tf tf
    """)
Exemple #11
0
def log(time: Union[str, XArrow] = "today",
        *,
        detailed=True,
        groupby: Literal['t', 'tag'] = None) -> bool:
    if groupby and groupby not in ('t', 'tag'):
        raise ValueError(
            f"log({time = !r}, {detailed = }, {groupby = !r}) groupby must be either 't' | 'tag'"
        )
    work = store.load()
    if not work:
        raise EmptySheet()
    current = None
    arrow = XArrow.from_human(time)
    # now = arrow.now() # TODO: should support a range of times, spanning several days etc
    day = work[arrow.DDMMYY]
    if not day:
        raise NoActivities(arrow.DDMMYY)
    # _log = Log()
    activities: list[Activity] = day.values()
    # activity: Activity = activities[0]
    # entry: Entry = activity[0]
    # entry_timespan = entry.timespan
    # print(f'{entry_timespan = !r}')
    by_tag = defaultdict(set)  # activities = list(day.values());
    # for i, entry in enumerate(reversed(work)):
    # for day_key in reversed(work):
    # day: Day = work[day_key]
    # # day
    # # item = Entry(**entry)
    # # for i, item in enumerate(map(lambda w: Entry(**w), reversed(work))):
    # item_start = item.start
    # item_start_DDMMYY = item_start.DDMMYY
    # period_arrow_DDMMYY = period_arrow.DDMMYY
    # if item_start_DDMMYY != period_arrow_DDMMYY:
    # if period_arrow > item.start:
    # # We have iterated past period
    # break
    # continue
    # if period_arrow.month != item.start.month and period_arrow.year != item.start.year:
    # break
    # # noinspection PyUnreachableCode
    # 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)
    # log_entry = _log[item.name]
    # log_entry.name = item.name
    # item_notes = item.notes
    # log_entry_notes = log_entry.notes
    # log_entry_notes.extend(item_notes)
    # log_entry.tags |= item.tags
    # timespan = Timespan(item.start, item.end or now)
    # log_entry.timespans.append(timespan)
    # if not timespan.end:
    # log_entry.is_current = True
    title = c.title(f"{arrow.isoweekday('full')}, " + arrow.DDMMYY)
    now = XArrow.now()
    # ago = now.humanize(arrow, granularity=["year", "month", "week", "day", "hour", "minute"])
    ago = arrows2rel_time(now, arrow)
    if ago:
        title += f' {c.dim("| " + ago)}'

    print(title + '\n')
    # if not _log:
    #     return True
    name_column_width = max(
        *map(len, map(lambda _activity: _activity.name, activities)), 24) + 8
    if groupby:
        for _tag, names in by_tag.items():
            print(c.tag(_tag))
            for name in names:
                # noinspection PyUnresolvedReferences
                print_log(name, _log[name], current, detailed,
                          name_column_width)
        return True

    # for name, log_entry in _log.sorted_entries():
    for activity in activities:
        print(activity.pretty(detailed, name_column_width))

    print(
        c.title('Total: ') +
        re.sub(r'\d', lambda match: f'{c.digit(match.group())}',
               day.human_duration))
Exemple #12
0
def is_working() -> bool:
    data = store.load()
    return data and 'end' not in data[-1]