From 6ffecd93bc287d3654474fc6c834118c90c5cdc6 Mon Sep 17 00:00:00 2001 From: Cody Hiar Date: Sat, 3 Mar 2018 13:49:35 -0700 Subject: Initial Commit --- cli.py | 252 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100755 cli.py (limited to 'cli.py') diff --git a/cli.py b/cli.py new file mode 100755 index 0000000..8b7afba --- /dev/null +++ b/cli.py @@ -0,0 +1,252 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""CLI App.""" +import datetime +import getpass +import os + +from blessings import Terminal + +from fuzzyfinder import fuzzyfinder +from prompt_toolkit import prompt +from prompt_toolkit.completion import Completer, Completion +from prompt_toolkit.shortcuts import confirm +from pyactivecollab import ActiveCollab, Config + +t = Terminal() + + +class FuzzyCompleter(Completer): + """Fuzzy Completer Alpha Sorted.""" + + def __init__(self, words): + """Initialize.""" + self.words = words + + def get_completions(self, document, complete_event): + """Use fuzzyfinder for completions.""" + word_before_cursor = document.text_before_cursor + words = fuzzyfinder(word_before_cursor, self.words) + for x in words: + yield Completion(x, -len(word_before_cursor)) + + +class DateFuzzyCompleter(Completer): + """Fuzzy Completer For Dates.""" + + def get_completions(self, document, complete_event): + """Use fuzzyfinder for date completions. + + The fuzzyfind auto sorts by alpha so this is to show dates relative to + the current date instead of by day of week. + """ + base = datetime.datetime.today() + date_format = '%a, %Y-%m-%d' + date_list = [(base - datetime.timedelta(days=x)).strftime(date_format) + for x in range(0, 30)] + word_before_cursor = document.text_before_cursor + words = fuzzyfinder(word_before_cursor, date_list) + + def sort_by_date(date_str: str): + return datetime.datetime.strptime(date_str, date_format) + + # Re-sort by date rather than day name + words = sorted(words, key=sort_by_date, reverse=True) + for x in words: + yield Completion(x, -len(word_before_cursor)) + + +class WeekFuzzyCompleter(Completer): + """Fuzzy Completer For Weeks.""" + + def get_completions(self, document, complete_event): + """Use fuzzyfinder for week completions.""" + def datetime_to_week_str(dt: datetime.datetime): + """Convert a datetime to weekstring. + + datetime.datetime(2018, 2, 26, 0, 0) => '2018-02-26 to 2018-03-04' + """ + if dt.weekday() != 0: + monday_dt = dt - datetime.timedelta(days=dt.weekday()) + sunday_dt = monday_dt + datetime.timedelta(days=6) + return '{} to {}'.format( + monday_dt.strftime('%Y-%m-%d'), sunday_dt.strftime('%Y-%m-%d')) + + base = datetime.datetime.today() + week_list = [datetime_to_week_str(base - datetime.timedelta(weeks=x)) + for x in range(0, 5)] + word_before_cursor = document.text_before_cursor + words = fuzzyfinder(word_before_cursor, week_list) + words = sorted(words, reverse=True) + for x in words: + yield Completion(x, -len(word_before_cursor)) + + +def timestamp_to_datetime(json, fieldname): + """Convert field from timestamp to datetime for json. + + Currenlty hardcoded to MST timezone + """ + timestamp = json[fieldname] + mst_hours = datetime.timedelta(hours=7) + json[fieldname] = datetime.datetime.fromtimestamp(timestamp) + mst_hours + return json + + +def create_time_record(ac): + """Super Innefficient calls to create a time record.""" + # Get Project + projects = ac.get_projects() + suggestions = [x['name'] for x in projects] + completer = FuzzyCompleter(suggestions) + text = prompt('(Project)> ', completer=completer) + project = next(x for x in projects if x['name'] == text) + # Value + value = prompt('(Value)> ') + # If integer is passed then treat it as minutes + if ('.' not in value) and (':' not in value): + value = float(value) / 60 + # Job Type + job_types = ac.get_job_types() + suggestions = [x['name'] for x in job_types] + completer = FuzzyCompleter(suggestions) + text = prompt('(Job Type)> ', completer=completer) + job_type = next(x for x in job_types if x['name'] == text) + # Date + completer = DateFuzzyCompleter() + text = prompt('(Date)> ', completer=completer) + choosen_date = datetime.datetime.strptime(text, '%a, %Y-%m-%d') + # Billable + billable_choices = {True: 1, False: 0} + billable = confirm('(Billable (y/n))> ') + billable = billable_choices[billable] + # Summary + summary = prompt('(Summary)> ', enable_open_in_editor=True) + # User + users = ac.get_users() + user = next(x for x in users if x['email'] == config.user) + data = { + 'value': value, + 'user_id': user['id'], + 'job_type_id': job_type['id'], + 'record_date': choosen_date.strftime('%Y-%m-%d'), + 'billable_status': billable, + 'summary': summary + } + url = '/projects/{}/time-records'.format(project['id']) + ac.post(url, data) + + +def list_daily_time_records(ac): + """List current user's time entrys for a specific day.""" + os.system('cal -3') + # Make sure that the input is valid. This should be broken out to + # encapsulate all auto-complete inputs + completer = DateFuzzyCompleter() + valid = False + while not valid: + try: + date_str = prompt('(Date)> ', completer=completer) + choosen_date = datetime.datetime.strptime(date_str, '%a, %Y-%m-%d') + except ValueError: + print('Bad input, try again.') + else: + valid = True + users = ac.get_users() + user = next(x for x in users if x['email'] == config.user) + r = ac.get_time_records(user['id']) + time_records = r['time_records'] + time_records = [timestamp_to_datetime(x, 'record_date') for x in time_records] + daily_time_records = [x for x in time_records + if x['record_date'].date() == choosen_date.date()] + billable = 0 + non_billable = 0 + daily_hours = 0 + for record in daily_time_records: + if record['billable_status']: + print(t.green('{:<6} {}'.format(record['value'], record['summary'][:60]))) + billable += record['value'] + else: + print(t.blue('{:<6} {}'.format(record['value'], record['summary'][:60]))) + non_billable += record['value'] + daily_hours += record['value'] + print((t.yellow(str(daily_hours)) + ' ' + + t.green(str(billable)) + ' ' + + t.blue(str(non_billable)))) + + +def list_weekly_time_records(ac): + """List current user's time entrys for a specific week.""" + os.system('cal -3') + # Make sure that the input is valid. This should be broken out to + # encapsulate all auto-complete inputs + completer = WeekFuzzyCompleter() + valid = False + while not valid: + try: + week_str = prompt('(Week)> ', completer=completer) + monday_dt = datetime.datetime.strptime(week_str.split()[0], '%Y-%m-%d') + sunday_dt = datetime.datetime.strptime(week_str.split()[2], '%Y-%m-%d') + except ValueError: + print('Bad input, try again.') + else: + valid = True + users = ac.get_users() + user = next(x for x in users if x['email'] == config.user) + r = ac.get_time_records(user['id']) + time_records = r['time_records'] + time_records = [timestamp_to_datetime(x, 'record_date') for x in time_records] + weekly_time_records = [x for x in time_records if + x['record_date'].date() >= monday_dt.date() and + x['record_date'].date() <= sunday_dt.date()] + days = [(sunday_dt - datetime.timedelta(days=x)) for x in range(7)] + weekly_billable = 0 + weekly_non_billable = 0 + weekly_hours = 0 + for day in days: + billable = 0 + non_billable = 0 + daily_hours = 0 + if day.date() > datetime.datetime.now().date(): + continue + daily_time_records = [x for x in weekly_time_records if + x['record_date'].date() == day.date()] + print(day) + for record in daily_time_records: + if record['billable_status']: + print(t.green('{:<6} {}'.format(record['value'], record['summary'][:60]))) + billable += record['value'] + weekly_billable += record['value'] + else: + print(t.blue('{:<6} {}'.format(record['value'], record['summary'][:60]))) + non_billable += record['value'] + weekly_non_billable += record['value'] + daily_hours += record['value'] + weekly_hours += record['value'] + print((t.yellow(str(daily_hours)) + ' ' + + t.green(str(billable)) + ' ' + + t.blue(str(non_billable)))) + print('Weekly Hours') + print((t.yellow(str(weekly_hours)) + ' ' + + t.green(str(weekly_billable)) + ' ' + + t.blue(str(weekly_non_billable)))) + print('Percent Billable: {:.2f}%'.format(100 - ((37.5-weekly_billable)/37.5*100))) + + +# Load config, ensure password +config = Config() +config.load() +if not config.password: + config.password = getpass.getpass() + +ac = ActiveCollab(config) +ac.authenticate() +actions = { + 'Create Time Record': create_time_record, + 'List Daily Time Records': list_daily_time_records, + 'List Weekly Time Records': list_weekly_time_records, +} +completer = FuzzyCompleter(actions.keys()) +while True: + action = prompt('(Action)> ', completer=completer) + actions[action](ac) -- cgit v1.2.3