def setUp(self): taskwarrior = TaskWarrior(data_location='tests/test_data/.task', create=True) Task(taskwarrior, description='test_yesterday', schedule='yesterday', estimate='20min').save() Task(taskwarrior, description='test_9:00_to_10:11', schedule='today+9hr', estimate='71min', project='test').save() Task(taskwarrior, description='test_14:00_to_16:00', schedule='today+14hr', estimate='2hr').save() Task(taskwarrior, description='test_tomorrow', schedule='tomorrow', estimate='24min').save() self.tasks = taskwarrior.tasks.filter(status='pending') self.schedule = Schedule(tw_data_dir='tests/test_data/.task', tw_data_dir_create=True) self.schedule.load_tasks()
def __init__(self, refresh_rate=1, hide_empty=True, scheduled_before=None, scheduled_after=None, scheduled=None, completed=True, hide_projects=False): self.stdscr = curses.initscr() self.stdscr.nodelay(True) curses.noecho() self.refresh_rate = refresh_rate self.completed = completed self.scheduled = scheduled self.scheduled_before = scheduled_before self.scheduled_after = scheduled_after self.hide_projects = hide_projects self.hide_empty = hide_empty self.buffer = [] self.prev_buffer = [] self.init_colors() self.schedule = Schedule()
def __init__(self, argv): self.home_dir = os.path.expanduser("~") self.parse_args(argv) self.check_files() task_command_args = ["task", "status.not:deleted"] task_command_args.append(f"scheduled.after:{self.scheduled_after}") task_command_args.append(f"scheduled.before:{self.scheduled_before}") if not self.show_completed: task_command_args.append(f"status.not:{self.show_completed}") self.backend = PatchedTaskWarrior( data_location=self.data_location, create=False, taskrc_location=self.taskrc_location, task_command=" ".join(task_command_args), ) self.schedule = Schedule( self.backend, scheduled_after=self.scheduled_after, scheduled_before=self.scheduled_before, )
def setUp(self): taskwarrior = TaskWarrior(data_location='tests/test_data/.task', create=True) Task(taskwarrior, description='test_yesterday', schedule='yesterday', estimate='20min').save() Task(taskwarrior, description='test_9:00_to_10:11', schedule='today+9hr', estimate='71min').save() Task(taskwarrior, description='test_14:00_to_16:00', schedule='today+14hr', estimate='2hr').save() Task(taskwarrior, description='test_16:10_to_16:34', schedule='today+16hr+10min', estimate='24min').save() Task(taskwarrior, description='test_tomorrow', schedule='tomorrow', estimate='24min').save() self.schedule = Schedule(tw_data_dir='tests/test_data/.task', tw_data_dir_create=True)
def test_get_time_slots(self): schedule = Schedule( tw_data_dir="tests/test_data/.task", tw_data_dir_create=False, taskrc_location="tests/test_data/.taskrc", ) schedule.get_time_slots()
def test_clear_cache(self, schedule: Schedule): tasks = schedule.tasks assert tasks schedule.clear_cache() with pytest.raises(KeyError): schedule.__dict__["tasks"] tasks = schedule.tasks assert tasks
class ScheduledTaskTest(unittest.TestCase): def setUp(self): taskwarrior = TaskWarrior(data_location='tests/test_data/.task', create=True) Task(taskwarrior, description='test_yesterday', schedule='yesterday', estimate='20min').save() Task(taskwarrior, description='test_9:00_to_10:11', schedule='today+9hr', estimate='71min', project='test').save() Task(taskwarrior, description='test_14:00_to_16:00', schedule='today+14hr', estimate='2hr').save() Task(taskwarrior, description='test_tomorrow', schedule='tomorrow', estimate='24min').save() self.tasks = taskwarrior.tasks.filter(status='pending') self.schedule = Schedule(tw_data_dir='tests/test_data/.task', tw_data_dir_create=True) self.schedule.load_tasks() def tearDown(self): os.remove(os.path.dirname(__file__) + '/test_data/.task/backlog.data') os.remove( os.path.dirname(__file__) + '/test_data/.task/completed.data') os.remove(os.path.dirname(__file__) + '/test_data/.task/pending.data') os.remove(os.path.dirname(__file__) + '/test_data/.task/undo.data') def test_init_works_correctly(self): task = ScheduledTask(self.tasks[1], self.schedule) self.assertEqual(task.task, self.tasks[1]) self.assertEqual(task.active, False) self.assertEqual(task.completed, False) self.assertNotEqual(task.task_id, 0) self.assertEqual(task.project, 'test') date_str = datetime.now().strftime('%Y-%m-%d') self.assertEqual(str(task.description), 'test_9:00_to_10:11') self.assertEqual(str(task.start), '{} 09:00:00+02:00'.format(date_str)) self.assertEqual(str(task.end), '{} 10:11:00+02:00'.format(date_str)) self.assertEqual(task.should_be_active, False) def test_overdue_task_returns_true(self): task = ScheduledTask(self.tasks[0], self.schedule) self.assertEqual(task.overdue, True) def test_non_overdue_task_returns_false(self): task = ScheduledTask(self.tasks[3], self.schedule) self.assertEqual(task.overdue, False)
def test_get_timeslots_returns_correct_amount_of_days(self): schedule = Schedule( tw_data_dir="tests/test_data/.task", tw_data_dir_create=False, taskrc_location="tests/test_data/.taskrc", scheduled_before="tomorrow+3days", scheduled_after="tomorrow-3days", scheduled=None, ) output = schedule.get_time_slots() self.assertEqual(len(output), 7) schedule = Schedule( tw_data_dir="tests/test_data/.task", tw_data_dir_create=False, taskrc_location="tests/test_data/.taskrc", scheduled_before="today+15days", scheduled_after="today", scheduled=None, ) output = schedule.get_time_slots() self.assertEqual(len(output), 16) schedule = Schedule( tw_data_dir="tests/test_data/.task", tw_data_dir_create=False, taskrc_location="tests/test_data/.taskrc", scheduled_before=None, scheduled_after=None, scheduled="today", ) output = schedule.get_time_slots() self.assertEqual(len(output), 1)
def test_schedule_can_be_initialized(self): schedule = Schedule( tw_data_dir="tests/test_data/.task", tw_data_dir_create=False, taskrc_location="tests/test_data/.taskrc", ) assert schedule is not None
def test_get_time_slots_has_correct_tasks(self, schedule: Schedule): time_slots = schedule.get_time_slots() yesterday = calculate_datetime("yesterday").date().isoformat() today = calculate_datetime("today").date().isoformat() tomorrow = calculate_datetime("tomorrow").date().isoformat() assert time_slots[yesterday]["00"][0][ "description"] == "test_yesterday" assert time_slots[today]["09"][0][ "description"] == "test_9:00_to_10:11" assert time_slots[today]["14"][0][ "description"] == "test_14:00_to_16:00" assert time_slots[today]["16"][0][ "description"] == "test_16:10_to_16:34" assert time_slots[tomorrow]["00"][0]["description"] == "test_tomorrow"
def schedule(tw): """Create a Schedule instance with a few tasks.""" ScheduledTask(tw, description="test_last_week", schedule="yesterday-7days", estimate="20min").save() ScheduledTask(tw, description="test_yesterday", schedule="yesterday", estimate="20min").save() ScheduledTask(tw, description="test_9:00_to_10:11", schedule="today+9hr", estimate="71min").save() ScheduledTask(tw, description="test_14:00_to_16:00", schedule="today+14hr", estimate="2hr").save() ScheduledTask( tw, description="test_16:10_to_16:34", schedule="today+16hr+10min", estimate="24min", ).save() ScheduledTask(tw, description="test_tomorrow", schedule="tomorrow", estimate="24min").save() ScheduledTask(tw, description="test_next_week", schedule="today+7days", estimate="20min").save() scheduled_after: datetime = calculate_datetime("tomorrow-3days") scheduled_before: datetime = calculate_datetime("tomorrow+3days") schedule = Schedule(backend=tw, scheduled_before=scheduled_before, scheduled_after=scheduled_after) yield schedule
def test_get_column_offsets(self, schedule: Schedule): offsets = schedule.get_column_offsets() assert offsets == [0, 5, 7, 19, 29, 37]
def setUp(self): self.taskrc_path = "tests/test_data/.taskrc" self.task_dir_path = "tests/test_data/.task" self.assertEqual(os.path.isdir(self.taskrc_path), False) self.assertEqual(os.path.isdir(self.task_dir_path), False) # Create a sample .taskrc with open(self.taskrc_path, "w+") as file: file.write("# User Defined Attributes\n") file.write("uda.estimate.type=duration\n") file.write("uda.estimate.label=Est\n") file.write("# User Defined Attributes\n") file.write("uda.tb_estimate.type=numeric\n") file.write("uda.tb_estimate.label=Est\n") file.write("uda.tb_real.type=numeric\n") file.write("uda.tb_real.label=Real\n") # Create a sample empty .task directory os.makedirs(self.task_dir_path) taskwarrior = TaskWarrior( data_location="tests/test_data/.task", create=True, taskrc_location="tests/test_data/.taskrc", ) Task( taskwarrior, description="test_yesterday", schedule="yesterday", estimate="20min", ).save() Task( taskwarrior, description="test_9:00_to_10:11", schedule="today+9hr", estimate="71min", ).save() Task( taskwarrior, description="test_14:00_to_16:00", schedule="today+14hr", estimate="2hr", ).save() Task( taskwarrior, description="test_16:10_to_16:34", schedule="today+16hr+10min", estimate="24min", ).save() Task( taskwarrior, description="test_tomorrow", schedule="tomorrow", estimate="24min", ).save() self.schedule = Schedule( tw_data_dir="tests/test_data/.task", tw_data_dir_create=False, taskrc_location="tests/test_data/.taskrc", )
class ScheduleTest(unittest.TestCase): def setUp(self): self.taskrc_path = "tests/test_data/.taskrc" self.task_dir_path = "tests/test_data/.task" self.assertEqual(os.path.isdir(self.taskrc_path), False) self.assertEqual(os.path.isdir(self.task_dir_path), False) # Create a sample .taskrc with open(self.taskrc_path, "w+") as file: file.write("# User Defined Attributes\n") file.write("uda.estimate.type=duration\n") file.write("uda.estimate.label=Est\n") file.write("# User Defined Attributes\n") file.write("uda.tb_estimate.type=numeric\n") file.write("uda.tb_estimate.label=Est\n") file.write("uda.tb_real.type=numeric\n") file.write("uda.tb_real.label=Real\n") # Create a sample empty .task directory os.makedirs(self.task_dir_path) taskwarrior = TaskWarrior( data_location="tests/test_data/.task", create=True, taskrc_location="tests/test_data/.taskrc", ) Task( taskwarrior, description="test_yesterday", schedule="yesterday", estimate="20min", ).save() Task( taskwarrior, description="test_9:00_to_10:11", schedule="today+9hr", estimate="71min", ).save() Task( taskwarrior, description="test_14:00_to_16:00", schedule="today+14hr", estimate="2hr", ).save() Task( taskwarrior, description="test_16:10_to_16:34", schedule="today+16hr+10min", estimate="24min", ).save() Task( taskwarrior, description="test_tomorrow", schedule="tomorrow", estimate="24min", ).save() self.schedule = Schedule( tw_data_dir="tests/test_data/.task", tw_data_dir_create=False, taskrc_location="tests/test_data/.taskrc", ) def tearDown(self): try: os.remove(self.taskrc_path) except FileNotFoundError: pass try: shutil.rmtree(self.task_dir_path) except FileNotFoundError: pass def test_schedule_can_be_initialized(self): schedule = Schedule( tw_data_dir="tests/test_data/.task", tw_data_dir_create=False, taskrc_location="tests/test_data/.taskrc", ) assert schedule is not None # TODO Move to /tests/functional/ # def test_get_tasks_returns_correct_tasks(self): # self.schedule.load_tasks() # date_str = datetime.datetime.now().strftime('%Y-%m-%d') # task: ScheduledTask = self.schedule.tasks[0] # assert str(task['description']) == 'test_9:00_to_10:11' # assert str(task['scheduled'])[0:-6] == '{} 09:00:00'.format(date_str) # assert str(task.scheduled_end_time)[0:-6] == '{} 10:11:00'.format(date_str) # task = self.schedule.tasks[1] # assert str(task['description']) == 'test_14:00_to_16:00' # assert str(task['start'])[0:-6] == '{} 14:00:00'.format(date_str) # assert str(task.scheduled_end_time)[0:-6] == '{} 16:00:00'.format(date_str) # task = self.schedule.tasks[2] # assert str(task['description']) == 'test_16:10_to_16:34' # assert str(task['start'])[0:-6] == '{} 16:10:00'.format(date_str) # assert str(task.scheduled_end_time)[0:-6] == '{} 16:34:00'.format(date_str) def test_get_calculated_date_returns_correct_values(self): calculated = self.schedule.get_calculated_date("today").date() expected = datetime.datetime.now().date() self.assertEqual(calculated, expected) calculated = self.schedule.get_calculated_date("now+1day").date() expected = datetime.datetime.now().date() + datetime.timedelta(days=1) self.assertEqual(calculated, expected) calculated = self.schedule.get_calculated_date("now+1week").date() expected = datetime.datetime.now().date() + datetime.timedelta(days=7) self.assertEqual(calculated, expected) def test_get_timeslots_returns_correct_amount_of_days(self): schedule = Schedule( tw_data_dir="tests/test_data/.task", tw_data_dir_create=False, taskrc_location="tests/test_data/.taskrc", scheduled_before="tomorrow+3days", scheduled_after="tomorrow-3days", scheduled=None, ) output = schedule.get_time_slots() self.assertEqual(len(output), 7) schedule = Schedule( tw_data_dir="tests/test_data/.task", tw_data_dir_create=False, taskrc_location="tests/test_data/.taskrc", scheduled_before="today+15days", scheduled_after="today", scheduled=None, ) output = schedule.get_time_slots() self.assertEqual(len(output), 16) schedule = Schedule( tw_data_dir="tests/test_data/.task", tw_data_dir_create=False, taskrc_location="tests/test_data/.taskrc", scheduled_before=None, scheduled_after=None, scheduled="today", ) output = schedule.get_time_slots() self.assertEqual(len(output), 1) # def test_format_task_returns_correct_format(self): # self.schedule.get_tasks() # task = self.schedule.tasks[0] # assert self.schedule.format_task(task) == [9, '○', 2, '09:00-10:11', # 'test_9:00_to_10:11'] # task = self.schedule.tasks[1] # assert self.schedule.format_task(task) == [14, '○', 3, '14:00-16:00', # 'test_14:00_to_16:00'] # task = self.schedule.tasks[2] # assert self.schedule.format_task(task) == [16, '○', 4, '16:10-16:34', # 'test_16:10_to_16:34'] # def test_format_as_table_returns_correct_format(self): # expected_rows = [ # ' ID Time Description', # ' 0', # ' 1', # ' 2', # ' 3', # ' 4', # ' 5', # ' 6', # ' 7', # ' 8', # ' 9 ○ 2 09:00-10:11 test_9:00_to_10:11', # '10', # '11', # '12', # '13', # '14 ○ 3 14:00-16:00 test_14:00_to_16:00', # '15', # '16 ○ 4 16:10-16:34 test_16:10_to_16:34', # '17', # '18', # '19', # '20', # '21', # '22', # '23' # ] # self.schedule.get_tasks() # table = self.schedule.format_as_table(hide_empty=False) # rows = table.split('\n') # assert rows == expected_rows # def test_format_as_table_hide_empty_returns_correct_format(self): # expected_rows = [ # ' ID Time Description', # ' 8', # ' 9 ○ 2 09:00-10:11 test_9:00_to_10:11', # '10', # '11', # '12', # '13', # '14 ○ 3 14:00-16:00 test_14:00_to_16:00', # '15', # '16 ○ 4 16:10-16:34 test_16:10_to_16:34', # '17' # ] # self.schedule.get_tasks() # table = self.schedule.format_as_table(hide_empty=True) # rows = table.split('\n') # assert rows == expected_rows # def test_cli_returns_0(self): # process = subprocess.run(['python3 taskschedule/taskschedule.py'], # shell=True, # timeout=10, # stdout=subprocess.PIPE, # stderr=subprocess.PIPE, check=True) # assert process.returncode == 0 def test_get_time_slots(self): schedule = Schedule( tw_data_dir="tests/test_data/.task", tw_data_dir_create=False, taskrc_location="tests/test_data/.taskrc", ) schedule.get_time_slots() def test_align_matrix(self): rows = [ ["", "", "ID", "Time", "Description"], ["8", "", "", "", ""], ["9", "○", "2", "09:00-10:11", "test_9:00_to_10:11"], ["10", "", "", "", ""], ["11", "", "", "", ""], ["14", "○", "654", "12:00", "test_12:00"], ] returned_rows = self.schedule.align_matrix(rows) expected_rows = [ [" ", " ", "ID ", "Time ", "Description "], ["8 ", " ", " ", " ", " "], ["9 ", "○", "2 ", "09:00-10:11", "test_9:00_to_10:11"], ["10", " ", " ", " ", " "], ["11", " ", " ", " ", " "], ["14", "○", "654", "12:00 ", "test_12:00 "], ] assert returned_rows == expected_rows
class Screen(): """This class handles the rendering of the schedule.""" def __init__(self, refresh_rate=1, hide_empty=True, scheduled_before=None, scheduled_after=None, scheduled=None, completed=True, hide_projects=False): self.stdscr = curses.initscr() self.stdscr.nodelay(True) curses.noecho() self.refresh_rate = refresh_rate self.completed = completed self.scheduled = scheduled self.scheduled_before = scheduled_before self.scheduled_after = scheduled_after self.hide_projects = hide_projects self.hide_empty = hide_empty self.buffer = [] self.prev_buffer = [] self.init_colors() self.schedule = Schedule() def close(self): """Close the curses screen.""" curses.endwin() def init_colors(self): """Initialize the colors.""" curses.curs_set(0) curses.start_color() curses.init_pair(1, 20, curses.COLOR_BLACK) curses.init_pair(2, 8, 0) # Hours curses.init_pair(3, 20, 234) # Alternating background curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_BLACK) # Header curses.init_pair(5, curses.COLOR_GREEN, curses.COLOR_BLACK) # Current hour curses.init_pair(6, 19, 234) # Completed task - alternating background curses.init_pair(7, 19, 0) # Completed task curses.init_pair(8, curses.COLOR_BLACK, curses.COLOR_GREEN) # Active task curses.init_pair(9, curses.COLOR_BLACK, curses.COLOR_BLACK) # Glyph curses.init_pair(10, curses.COLOR_GREEN, curses.COLOR_BLACK) # Active task curses.init_pair(11, curses.COLOR_YELLOW, curses.COLOR_BLACK) # Overdue task curses.init_pair(12, curses.COLOR_YELLOW, 234) # Overdue task alt curses.init_pair(13, curses.COLOR_GREEN, 234) # Should-be-active task alt # pylint: disable=invalid-name self.COLOR_DEFAULT = curses.color_pair(1) self.COLOR_DEFAULT_ALTERNATE = curses.color_pair(3) self.COLOR_HEADER = curses.color_pair(4) | curses.A_UNDERLINE self.COLOR_HOUR = curses.color_pair(2) self.COLOR_HOUR_CURRENT = curses.color_pair(5) self.COLOR_ACTIVE = curses.color_pair(8) self.COLOR_SHOULD_BE_ACTIVE = curses.color_pair(10) self.COLOR_SHOULD_BE_ACTIVE_ALTERNATE = curses.color_pair(13) self.COLOR_OVERDUE = curses.color_pair(11) self.COLOR_OVERDUE_ALTERNATE = curses.color_pair(12) self.COLOR_COMPLETED = curses.color_pair(7) self.COLOR_COMPLETED_ALTERNATE = curses.color_pair(6) self.COLOR_GLYPH = curses.color_pair(9) def get_task_color(self, task, alternate): """Return the color for the given task.""" color = None if task.completed: if alternate: color = self.COLOR_COMPLETED_ALTERNATE else: color = self.COLOR_COMPLETED elif task.active: color = self.COLOR_ACTIVE elif task.should_be_active: if alternate: color = self.COLOR_SHOULD_BE_ACTIVE_ALTERNATE else: color = self.COLOR_SHOULD_BE_ACTIVE elif task.overdue and not task.completed: if alternate: color = self.COLOR_OVERDUE_ALTERNATE else: color = self.COLOR_OVERDUE else: if alternate: color = self.COLOR_DEFAULT_ALTERNATE else: color = self.COLOR_DEFAULT return color def draw_footnote(self, y): if self.scheduled_before and self.scheduled_after: footnote = '{} tasks - from {} until {}'.format( len(self.schedule.tasks), self.scheduled_after, self.scheduled_before) else: footnote = '{} tasks - {}'.format(len(self.schedule.tasks), self.scheduled) self.stdscr.addstr(y + 2, 0, footnote, self.COLOR_DEFAULT) def draw(self): """Draw the current buffer.""" last_line = 0 if not self.buffer: self.stdscr.clear() self.stdscr.addstr(0, 0, 'No tasks to display.', self.COLOR_DEFAULT) self.draw_footnote(last_line) self.stdscr.refresh() else: if self.prev_buffer != self.buffer: self.stdscr.clear() for line, offset, string, color in self.buffer: max_y, max_x = self.stdscr.getmaxyx() if line < max_y - 2: self.stdscr.addstr(line, offset, string, color) self.stdscr.refresh() else: break last_line = line self.draw_footnote(last_line) self.stdscr.refresh() def refresh_buffer(self): """Refresh the buffer.""" max_y, max_x = self.stdscr.getmaxyx() self.prev_buffer = self.buffer self.buffer = [] self.schedule.load_tasks(scheduled_before=self.scheduled_before, scheduled_after=self.scheduled_after, scheduled=self.scheduled, completed=self.completed) if not self.schedule.tasks: return as_dict = self.schedule.as_dict() # Determine offsets offsets = self.schedule.get_column_offsets() max_project_column_length = round(max_x / 8) if offsets[4] - offsets[3] > max_project_column_length: offsets[4] = offsets[3] + max_project_column_length # Draw headers headers = ['', '', 'ID', 'Time', 'Project', 'Description'] column_lengths = [2, 1] column_lengths.append(self.schedule.get_max_length('id')) column_lengths.append(11) column_lengths.append(max_project_column_length - 1) column_lengths.append(self.schedule.get_max_length('description')) for i, header in enumerate(headers): try: extra_length = column_lengths[i] - len(header) headers[i] += ' ' * extra_length except IndexError: pass self.buffer.append((0, offsets[1], headers[2], self.COLOR_HEADER)) self.buffer.append((0, offsets[2], headers[3], self.COLOR_HEADER)) self.buffer.append((0, offsets[3], headers[4], self.COLOR_HEADER)) if not self.hide_projects: self.buffer.append((0, offsets[4], headers[5], self.COLOR_HEADER)) # Draw schedule alternate = True current_line = 1 if self.hide_empty: first_task = self.schedule.tasks[0].start first_hour = first_task.hour last_task = self.schedule.tasks[-1].start last_hour = last_task.hour else: first_hour = 0 last_hour = 23 for i in range(first_hour, last_hour + 1): tasks = as_dict[i] if not tasks: # Add empty line if alternate: color = self.COLOR_DEFAULT_ALTERNATE else: color = self.COLOR_DEFAULT # Fill line to screen length self.buffer.append((current_line, 5, ' ' * (max_x - 5), color)) # Draw hour column, highlight current hour current_hour = time.localtime().tm_hour if i == current_hour: self.buffer.append( (current_line, 0, str(i), self.COLOR_HOUR_CURRENT)) else: self.buffer.append( (current_line, 0, str(i), self.COLOR_HOUR)) current_line += 1 alternate = not alternate for ii, task in enumerate(tasks): color = self.get_task_color(task, alternate) # Only draw hour once for multiple tasks if ii == 0: hour = str(i) else: hour = '' # Draw hour column, highlight current hour current_hour = time.localtime().tm_hour if hour != '': if int(hour) == current_hour: self.buffer.append( (current_line, 0, hour, self.COLOR_HOUR_CURRENT)) else: self.buffer.append( (current_line, 0, hour, self.COLOR_HOUR)) # Fill line to screen length self.buffer.append((current_line, 5, ' ' * (max_x - 5), color)) # Draw glyph column self.buffer.append( (current_line, 3, task.glyph, self.COLOR_GLYPH)) # Draw task id column if task.task_id != 0: self.buffer.append( (current_line, 5, str(task.task_id), color)) # Draw time column if task.end is None: formatted_time = '{}'.format(task.start_time) else: end_time = '{}'.format(task.end.strftime('%H:%M')) formatted_time = '{}-{}'.format(task.start_time, end_time) self.buffer.append( (current_line, offsets[2], formatted_time, color)) # Optionally draw project column offset = 0 if not self.hide_projects: if task.project is None: project = '' else: max_length = offsets[4] - offsets[3] - 1 project = task.project[0:max_length] self.buffer.append( (current_line, offsets[3], project, color)) offset = offsets[4] else: offset = offsets[3] # Draw description column description = task.description[0:max_x - offset] self.buffer.append((current_line, offset, description, color)) current_line += 1 alternate = not alternate
def test_get_time_slots_returns_correct_amount_of_days( self, schedule: Schedule): time_slots = schedule.get_time_slots() assert len(time_slots) == 7
def test_get_max_length(self, schedule: Schedule): length = schedule.get_max_length("description") assert length == 19
class TaskscheduleTest(unittest.TestCase): def setUp(self): taskwarrior = TaskWarrior(data_location='tests/test_data/.task', create=True) Task(taskwarrior, description='test_yesterday', schedule='yesterday', estimate='20min').save() Task(taskwarrior, description='test_9:00_to_10:11', schedule='today+9hr', estimate='71min').save() Task(taskwarrior, description='test_14:00_to_16:00', schedule='today+14hr', estimate='2hr').save() Task(taskwarrior, description='test_16:10_to_16:34', schedule='today+16hr+10min', estimate='24min').save() Task(taskwarrior, description='test_tomorrow', schedule='tomorrow', estimate='24min').save() self.schedule = Schedule(tw_data_dir='tests/test_data/.task', tw_data_dir_create=True) def tearDown(self): os.remove(os.path.dirname(__file__) + '/test_data/.task/backlog.data') os.remove( os.path.dirname(__file__) + '/test_data/.task/completed.data') os.remove(os.path.dirname(__file__) + '/test_data/.task/pending.data') os.remove(os.path.dirname(__file__) + '/test_data/.task/undo.data') def test_schedule_can_be_initialized(self): schedule = Schedule() assert schedule is not None def test_get_tasks_returns_correct_tasks(self): self.schedule.load_tasks() date_str = datetime.now().strftime('%Y-%m-%d') task = self.schedule.tasks[0] assert str(task.description) == 'test_9:00_to_10:11' assert str(task.start) == '{} 09:00:00+02:00'.format(date_str) assert str(task.end) == '{} 10:11:00+02:00'.format(date_str) task = self.schedule.tasks[1] assert str(task.description) == 'test_14:00_to_16:00' assert str(task.start) == '{} 14:00:00+02:00'.format(date_str) assert str(task.end) == '{} 16:00:00+02:00'.format(date_str) task = self.schedule.tasks[2] assert str(task.description) == 'test_16:10_to_16:34' assert str(task.start) == '{} 16:10:00+02:00'.format(date_str) assert str(task.end) == '{} 16:34:00+02:00'.format(date_str) # def test_format_task_returns_correct_format(self): # self.schedule.get_tasks() # task = self.schedule.tasks[0] # assert self.schedule.format_task(task) == [9, '○', 2, '09:00-10:11', # 'test_9:00_to_10:11'] # task = self.schedule.tasks[1] # assert self.schedule.format_task(task) == [14, '○', 3, '14:00-16:00', # 'test_14:00_to_16:00'] # task = self.schedule.tasks[2] # assert self.schedule.format_task(task) == [16, '○', 4, '16:10-16:34', # 'test_16:10_to_16:34'] # def test_format_as_table_returns_correct_format(self): # expected_rows = [ # ' ID Time Description', # ' 0', # ' 1', # ' 2', # ' 3', # ' 4', # ' 5', # ' 6', # ' 7', # ' 8', # ' 9 ○ 2 09:00-10:11 test_9:00_to_10:11', # '10', # '11', # '12', # '13', # '14 ○ 3 14:00-16:00 test_14:00_to_16:00', # '15', # '16 ○ 4 16:10-16:34 test_16:10_to_16:34', # '17', # '18', # '19', # '20', # '21', # '22', # '23' # ] # self.schedule.get_tasks() # table = self.schedule.format_as_table(hide_empty=False) # rows = table.split('\n') # assert rows == expected_rows # def test_format_as_table_hide_empty_returns_correct_format(self): # expected_rows = [ # ' ID Time Description', # ' 8', # ' 9 ○ 2 09:00-10:11 test_9:00_to_10:11', # '10', # '11', # '12', # '13', # '14 ○ 3 14:00-16:00 test_14:00_to_16:00', # '15', # '16 ○ 4 16:10-16:34 test_16:10_to_16:34', # '17' # ] # self.schedule.get_tasks() # table = self.schedule.format_as_table(hide_empty=True) # rows = table.split('\n') # assert rows == expected_rows # def test_cli_returns_0(self): # process = subprocess.run(['python3 taskschedule/taskschedule.py'], # shell=True, # timeout=10, # stdout=subprocess.PIPE, # stderr=subprocess.PIPE, check=True) # assert process.returncode == 0 def test_cli_help_returns_help_message(self): process = subprocess.run(['python3 __main__.py -h'], shell=True, timeout=10, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True) output = process.stdout.split(b'\n') assert output[0].startswith(b'usage:') def test_align_matrix(self): rows = [ ['', '', 'ID', 'Time', 'Description'], ['8', '', '', '', ''], ['9', '○', '2', '09:00-10:11', 'test_9:00_to_10:11'], ['10', '', '', '', ''], ['11', '', '', '', ''], ['14', '○', '654', '12:00', 'test_12:00'], ] returned_rows = self.schedule.align_matrix(rows) expected_rows = [ [' ', ' ', 'ID ', 'Time ', 'Description '], ['8 ', ' ', ' ', ' ', ' '], ['9 ', '○', '2 ', '09:00-10:11', 'test_9:00_to_10:11'], ['10', ' ', ' ', ' ', ' '], ['11', ' ', ' ', ' ', ' '], ['14', '○', '654', '12:00 ', 'test_12:00 '], ] assert returned_rows == expected_rows
def test_get_next_task_returns_next_task(self, schedule: Schedule): next_task = schedule.get_next_task(schedule.tasks[2]) if next_task: assert next_task["description"] == "test_14:00_to_16:00" else: pytest.fail("A next task was expected but not returned.")
def test_get_next_task_for_last_task_returns_none(self, schedule: Schedule): next_task = schedule.get_next_task(schedule.tasks[6]) assert not next_task
class Main: def __init__(self, argv): self.home_dir = os.path.expanduser("~") self.parse_args(argv) self.check_files() task_command_args = ["task", "status.not:deleted"] task_command_args.append(f"scheduled.after:{self.scheduled_after}") task_command_args.append(f"scheduled.before:{self.scheduled_before}") if not self.show_completed: task_command_args.append(f"status.not:{self.show_completed}") self.backend = PatchedTaskWarrior( data_location=self.data_location, create=False, taskrc_location=self.taskrc_location, task_command=" ".join(task_command_args), ) self.schedule = Schedule( self.backend, scheduled_after=self.scheduled_after, scheduled_before=self.scheduled_before, ) def check_files(self): """Check if the required files, directories and settings are present.""" # Create a temporary taskwarrior instance to read the config taskwarrior = TaskWarrior( data_location=self.data_location, create=False, taskrc_location=self.taskrc_location, ) # Disable _forcecolor because it breaks tw config output taskwarrior.overrides.update({"_forcecolor": "off"}) # Check taskwarrior directory and taskrc if os.path.isdir(self.data_location) is False: raise TaskDirDoesNotExistError(".task directory not found") if os.path.isfile(self.taskrc_location) is False: raise TaskrcDoesNotExistError(".taskrc not found") # Check if required UDAs exist if taskwarrior.config.get("uda.estimate.type") is None: raise UDADoesNotExistError( ("uda.estimate.type does not exist " "in .taskrc") ) if taskwarrior.config.get("uda.estimate.label") is None: raise UDADoesNotExistError( ("uda.estimate.label does not exist " "in .taskrc") ) # Check sound file sound_file = self.home_dir + "/.taskschedule/hooks/drip.wav" if self.show_notifications and os.path.isfile(sound_file) is False: raise SoundDoesNotExistError( f"The specified sound file does not exist: {sound_file}" ) # Create user directory if it does not exist taskschedule_dir = self.home_dir + "/.taskschedule" hooks_directory = self.home_dir + "/.taskschedule/hooks" if not os.path.isdir(taskschedule_dir): os.mkdir(taskschedule_dir) if not os.path.isdir(hooks_directory): os.mkdir(hooks_directory) def parse_args(self, argv): parser = argparse.ArgumentParser( description="""Display a schedule report for taskwarrior.""" ) parser.add_argument( "-r", "--refresh", help="refresh every n seconds", type=int, default=1 ) parser.add_argument( "--from", help="scheduled from date: ex. 'today', 'tomorrow'", type=str, dest="after", default="today-1s", ) parser.add_argument( "--to", "--until", help="scheduled until date: ex. 'today', 'tomorrow'", type=str, dest="before", default="tomorrow", ) parser.add_argument( "-d", "--data-location", help="""data location (e.g. ~/.task)""", type=str, dest="data_location", default=f"{self.home_dir}/.task", ) parser.add_argument( "-t", "--taskrc-location", help="""taskrc location (e.g. ~/.taskrc)""", type=str, dest="taskrc_location", default=f"{self.home_dir}/.taskrc", ) parser.add_argument( "-a", "--all", help="show all hours, even if empty", action="store_true", default=False, ) parser.add_argument( "-c", "--completed", help="hide completed tasks", action="store_false", default=True, ) parser.add_argument( "-p", "--project", help="hide project column", action="store_true", default=False, ) parser.add_argument( "--no-notifications", help="disable notifications", action="store_false", default=True, dest="notifications", ) args = parser.parse_args(argv) if args.before and not args.after or not args.before and args.after: print( "Error: Either both --until and --from or neither options must be used." ) sys.exit(1) self.data_location = args.data_location self.taskrc_location = args.taskrc_location # Parse schedule date range self.scheduled_after: datetime = calculate_datetime(args.after) self.scheduled_before: datetime = calculate_datetime(args.before) self.show_completed = args.completed self.hide_empty = not args.all self.hide_projects = args.project self.refresh_rate = args.refresh self.show_notifications = args.notifications def main(self): """Initialize the screen and notifier, and start the main loop of the interface.""" if self.show_notifications: self.notifier = Notifier(self.backend) else: self.notifier = None self.screen = Screen( self.schedule, scheduled_after=self.scheduled_after, scheduled_before=self.scheduled_before, hide_empty=self.hide_empty, hide_projects=self.hide_projects, ) try: self.run() except TaskDirDoesNotExistError as err: print("Error: {}".format(err)) sys.exit(1) except TaskrcDoesNotExistError as err: print("Error: {}".format(err)) sys.exit(1) except KeyboardInterrupt: self.screen.close() except ValueError as err: self.screen.close() print("Error: {}".format(err)) sys.exit(1) except UDADoesNotExistError as err: self.screen.close() print("Error: {}".format(err)) sys.exit(1) except SoundDoesNotExistError as err: self.screen.close() print("Error: {}".format(err)) sys.exit(1) else: try: self.screen.close() except curses_error as err: print(err.with_traceback) def run(self): """The main loop of the interface.""" filename = f"{self.data_location}/pending.data" cached_stamp = 0.0 last_refresh_time = 0.0 while True: key = self.screen.stdscr.getch() if key == 113: # q break elif key == 65 or key == 107: # Up / k self.screen.scroll(-1) last_refresh_time = time.time() elif key == 66 or key == 106: # Down / j self.screen.scroll(1) last_refresh_time = time.time() elif key == 54: # Page down max_y, max_x = self.screen.get_maxyx() self.screen.scroll(max_y - 4) last_refresh_time = time.time() elif key == 53: # Page up max_y, max_x = self.screen.get_maxyx() self.screen.scroll(-(max_y - 4)) last_refresh_time = time.time() elif key == KEY_RESIZE: last_refresh_time = time.time() self.screen.refresh_buffer() self.screen.draw() elif time.time() > last_refresh_time + self.refresh_rate: if self.notifier: self.notifier.send_notifications() # Redraw if task data has changed stamp = os.stat(filename).st_mtime if stamp != cached_stamp: cached_stamp = stamp self.schedule.clear_cache() self.screen.refresh_buffer() self.screen.draw() last_refresh_time = time.time() napms(1) if self.refresh_rate < 0: break
def test_schedule_can_be_initialized(self): schedule = Schedule() assert schedule is not None
def create_schedule(self): schedule = Schedule(tw_data_dir=self.task_dir_path, taskrc_location=self.taskrc_path) schedule.load_tasks()