Beispiel #1
0
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)
Beispiel #2
0
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
Beispiel #3
0
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
Beispiel #4
0
 def create_schedule(self):
     schedule = Schedule(tw_data_dir=self.task_dir_path,
                         taskrc_location=self.taskrc_path)
     schedule.load_tasks()