예제 #1
0
    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()
예제 #2
0
    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()
예제 #3
0
파일: main.py 프로젝트: nnist/taskschedule
    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,
        )
예제 #4
0
    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)
예제 #5
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()
예제 #6
0
    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
예제 #7
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)
예제 #8
0
    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)
예제 #9
0
 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
예제 #10
0
    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"
예제 #11
0
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
예제 #12
0
 def test_get_column_offsets(self, schedule: Schedule):
     offsets = schedule.get_column_offsets()
     assert offsets == [0, 5, 7, 19, 29, 37]
예제 #13
0
    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",
        )
예제 #14
0
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
예제 #15
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
예제 #16
0
 def test_get_time_slots_returns_correct_amount_of_days(
         self, schedule: Schedule):
     time_slots = schedule.get_time_slots()
     assert len(time_slots) == 7
예제 #17
0
 def test_get_max_length(self, schedule: Schedule):
     length = schedule.get_max_length("description")
     assert length == 19
예제 #18
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
예제 #19
0
 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.")
예제 #20
0
 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
예제 #21
0
파일: main.py 프로젝트: nnist/taskschedule
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
예제 #22
0
 def test_schedule_can_be_initialized(self):
     schedule = Schedule()
     assert schedule is not None
예제 #23
0
 def create_schedule(self):
     schedule = Schedule(tw_data_dir=self.task_dir_path,
                         taskrc_location=self.taskrc_path)
     schedule.load_tasks()