Exemplo n.º 1
0
def parse(text: str) -> Dict:
    """Parse contents of the quick add input."""

    result = {
        'title': '',
        'tags': set(),
        'start': None,
        'due': None,
        'recurring': None
    }

    for match in re.finditer(TAG_REGEX, text):
        data = match.group(0)
        result['tags'].add(data[1:])

    for match in re.finditer(TOKEN_REGEX, text):
        token = match.group(2)
        data = match.group(3)
        matched = False

        if token in TAGS_TOKEN:
            for tag in data.split(','):
                if tag:
                    # Strip @
                    if tag.startswith('@'):
                        tag = tag[1:]

                    result['tags'].add(tag)

            matched = True

        elif token in START_TOKEN:
            try:
                result['start'] = Date.parse(data)
                matched = True
            except ValueError:
                pass

        elif token in DUE_TOKEN:
            try:
                result['due'] = Date.parse(data)
                matched = True
            except ValueError:
                pass

        elif token in REPEAT_TOKEN:
            try:
                Date.today().parse_from_date(data)
                result['recurring'] = data
                matched = True
            except ValueError:
                pass

        # Remove this part from the title
        if matched:
            text = text.replace(match[0], '')

    result['title'] = text
    return result
Exemplo n.º 2
0
def parse(text: str) -> Dict:
    """Parse contents of the quick add input."""

    result = {
        'title': '',
        'tags': set(),
        'start': None,
        'due': None,
        'recurring': None
    }

    for match in re.finditer(TAG_REGEX, text):
        data = match.group(0)
        result['tags'].add(data[1:])

    for match in re.finditer(TOKEN_REGEX, text):
        token = match.group(2)
        data = match.group(3)

        if token in TAGS_TOKEN:
            for t in data.split(','):
                # Strip @
                if t.startswith('@'):
                    t = t[1:]

                result['tags'].add(t)

        elif token in START_TOKEN:
            try:
                result['start'] = Date.parse(data)
            except ValueError:
                continue

        elif token in DUE_TOKEN:
            try:
                result['due'] = Date.parse(data)
            except ValueError:
                continue

        elif token in REPEAT_TOKEN:
            try:
                Date.today().parse_from_date(data)
                result['recurring'] = data
            except ValueError:
                continue

        # Remove this part from the title
        text = text[:match.start()] + text[match.end():]

    result['title'] = text
    return result
Exemplo n.º 3
0
Arquivo: task.py Projeto: snoord/gtg
    def set_status(self, status, donedate=None):
        old_status = self.status
        self.can_be_deleted = False
        # No need to update children or whatever if the task is not loaded
        if status and self.is_loaded():
            # we first modify the status of the children
            # If Done, we set the done date
            if status in [self.STA_DONE, self.STA_DISMISSED]:
                for c in self.get_subtasks():
                    if c.get_status() in [self.STA_ACTIVE]:
                        c.set_status(status, donedate=donedate)

                # If the task is recurring, it must be duplicate with 
                # another task id and the next occurence of the task
                # Furthermore, the duplicated task's parents
                # should be set the the parent's previous task
                # as well as the children's.
                # Because we want every subtask that is recurring 
                # to occur another time after its parent has been set to done or dismiss.
                # only recurring tasks without any recurring parent can be duplicated.
                if self.recurring and not self.is_parent_recurring():
                    nexttask_tid = self.duplicate_recursively()
                    if self.has_parent():
                        for p_tid in self.get_parents():
                            par = self.req.get_task(p_tid)
                            if (par.is_loaded() and par.get_status() in
                                (self.STA_ACTIVE)):
                                par.add_child(nexttask_tid)

            # If we mark a task as Active and that some parent are not
            # Active, we break the parent/child relation
            # It has no sense to have an active subtask of a done parent.
            # (old_status check is necessary to avoid false positive a start)
            elif status in [self.STA_ACTIVE] and\
                    old_status in [self.STA_DONE, self.STA_DISMISSED]:
                if self.has_parent():
                    for p_tid in self.get_parents():
                        par = self.req.get_task(p_tid)
                        if par.is_loaded() and par.get_status() in\
                                [self.STA_DONE, self.STA_DISMISSED]:
                            # we can either break the parent/child relationship
                            # self.remove_parent(p_tid)
                            # or restore the parent too
                            par.set_status(self.STA_ACTIVE)
                # We dont mark the children as Active because
                # They might be already completed after all

        # then the task itself
        if status:
            self.status = status

        # Set closing date
        if status and status in [self.STA_DONE, self.STA_DISMISSED]:
            # to the specified date (if any)
            if donedate:
                self.closed_date = donedate
            # or to today
            else:
                self.closed_date = Date.today()
        self.sync()
Exemplo n.º 4
0
    def test_toggle_dismiss_children(self):
        task = Task2(id=uuid4(), title='A Task')
        task2 = Task2(id=uuid4(), title='A Child Task')
        task.children.append(task2)
        task2.parent = task

        task.toggle_dismiss()
        self.assertEqual(task.status, Status.DISMISSED)
        self.assertEqual(task.date_closed, Date.today())
        self.assertEqual(task2.status, Status.DISMISSED)
        self.assertEqual(task2.date_closed, Date.today())

        task.toggle_dismiss()
        self.assertEqual(task.status, Status.ACTIVE)
        self.assertEqual(task.date_closed, Date.no_date())
        self.assertEqual(task2.status, Status.ACTIVE)
        self.assertEqual(task2.date_closed, Date.no_date())
Exemplo n.º 5
0
    def test_toggle_dismiss_single(self):
        task = Task2(id=uuid4(), title='A Task')

        task.toggle_dismiss()
        self.assertEqual(task.status, Status.DISMISSED)
        self.assertEqual(task.date_closed, Date.today())

        task.toggle_dismiss()
        self.assertEqual(task.status, Status.ACTIVE)
        self.assertEqual(task.date_closed, Date.no_date())
Exemplo n.º 6
0
    def show_popover_start(self, widget, event):
        """Open the start date calendar popup."""

        start_date = self.task.get_start_date() or Date.today()

        with signal_handler_block(self.start_calendar, self.start_handle):
            self.start_calendar.select_day(start_date.day)
            self.start_calendar.select_month(start_date.month - 1,
                                             start_date.year)

        self.start_popover.popup()
Exemplo n.º 7
0
    def test_toggle_active_single(self):
        task = Task2(id=uuid4(), title='A Task')

        self.assertEqual(task.status, Status.ACTIVE)

        task.toggle_active()
        self.assertEqual(task.status, Status.DONE)
        self.assertEqual(task.date_closed, Date.today())

        task.toggle_active()
        self.assertEqual(task.status, Status.ACTIVE)
        self.assertEqual(task.date_closed, Date.no_date())
Exemplo n.º 8
0
    def show_popover_due(self, widget, popover):
        """Open the due date calendar popup."""

        due_date = self.task.get_due_date()

        if not due_date or due_date.is_fuzzy():
            due_date = Date.today()

        with signal_handler_block(self.due_calendar, self.due_handle):
            self.due_calendar.select_day(due_date.day)
            self.due_calendar.select_month(due_date.month - 1, due_date.year)

        self.due_popover.popup()
Exemplo n.º 9
0
 def set_date(self, date, date_kind):
     self.__date_kind = date_kind
     if date_kind == GTGCalendar.DATE_KIND_DUE:
         self.__fuzzydate_btns.show()
     else:
         self.__fuzzydate_btns.hide()
     if not date:
         # we set the widget to today's date if there is not a date defined
         date = Date.today()
     self.__date = date
     if not date.is_fuzzy():
         self.__calendar.select_day(date.day)
         # Calendar use 0..11 for a month so we need -1
         # We can't use conversion through python's datetime
         # because it is often an invalid date
         self.__calendar.select_month(date.month - 1, date.year)
Exemplo n.º 10
0
    def toggle_active(self, propagate: bool = True) -> None:
        """Toggle between possible statuses."""

        if self.status is Status.ACTIVE:
            self.status = Status.DONE
            self.date_closed = Date.today()

        else:
            self.status = Status.ACTIVE
            self.date_closed = Date.no_date()

            if self.parent and self.parent.status is not Status.ACTIVE:
                self.parent.toggle_active(propagate=False)

        if propagate:
            for child in self.children:
                child.toggle_active()
Exemplo n.º 11
0
    def toggle_dismiss(self, propagate: bool = True) -> None:
        """Set this task to be dismissed."""

        if self.status is Status.ACTIVE:
            self.status = Status.DISMISSED
            self.date_closed = Date.today()

        elif self.status is Status.DISMISSED:
            self.status = Status.ACTIVE
            self.date_closed = Date.no_date()

            if self.parent and self.parent.status is not Status.ACTIVE:
                self.parent.toggle_dismiss(propagate=False)

        if propagate:
            for child in self.children:
                child.toggle_dismiss()
Exemplo n.º 12
0
    def check_commands(commands_list):
        """ Execute search commands

        This method is recursive for !or and !and """

        def fulltext_search(task, word):
            """ check if task contains the word """
            word = word.lower()
            text = task.get_excerpt(strip_tags=False).lower()
            title = task.get_title().lower()

            return word in text or word in title

        value_checks = {
            'after': lambda t, v: task.get_due_date() > v,
            'before': lambda t, v: task.get_due_date() < v,
            'tag': lambda t, v: v in task.get_tags_name(),
            'word': fulltext_search,
            'today': lambda task, v: task.get_due_date() == Date.today(),
            'tomorrow': lambda task, v: task.get_due_date() == Date.tomorrow(),
            'nodate': lambda task, v: task.get_due_date() == Date.no_date(),
            'now': lambda task, v: task.get_due_date() == Date.now(),
            'soon': lambda task, v: task.get_due_date() == Date.soon(),
            'someday': lambda task, v: task.get_due_date() == Date.someday(),
            'notag': lambda task, v: task.get_tags() == [],
        }

        for command in commands_list:
            cmd, positive, args = command[0], command[1], command[2:]
            result = False

            if cmd == 'or':
                for sub_cmd in args[0]:
                    if check_commands([sub_cmd]):
                        result = True
                        break
            elif value_checks.get(cmd, None):
                if len(args) > 0:
                    args = args[0]
                result = value_checks[cmd](task, args)

            if (positive and not result) or (not positive and result):
                return False

        return True
Exemplo n.º 13
0
    def purge_old_tasks(self, widget=None):
        """Remove closed tasks older than N days."""

        log.debug("Deleting old tasks")

        today = Date.today()
        max_days = self.config.get('autoclean_days')
        closed_tree = self.req.get_tasks_tree(name='inactive')

        closed_tasks = [self.req.get_task(tid) for tid in
                        closed_tree.get_all_nodes()]

        to_remove = [t for t in closed_tasks
                     if (today - t.get_closed_date()).days > max_days]

        [self.req.delete_task(task.get_id())
         for task in to_remove
         if self.req.has_task(task.get_id())]
Exemplo n.º 14
0
    def set_status(self, status, donedate=None):
        old_status = self.status
        self.can_be_deleted = False
        # No need to update children or whatever if the task is not loaded
        if status and self.is_loaded():
            # we first modify the status of the children
            # If Done, we set the done date
            if status in [self.STA_DONE, self.STA_DISMISSED]:
                for c in self.get_subtasks():
                    if c.get_status() in [self.STA_ACTIVE]:
                        c.set_status(status, donedate=donedate)
            # If we mark a task as Active and that some parent are not
            # Active, we break the parent/child relation
            # It has no sense to have an active subtask of a done parent.
            # (old_status check is necessary to avoid false positive a start)
            elif status in [self.STA_ACTIVE] and\
                    old_status in [self.STA_DONE, self.STA_DISMISSED]:
                if self.has_parent():
                    for p_tid in self.get_parents():
                        par = self.req.get_task(p_tid)
                        if par.is_loaded() and par.get_status() in\
                                [self.STA_DONE, self.STA_DISMISSED]:
                            # we can either break the parent/child relationship
                            # self.remove_parent(p_tid)
                            # or restore the parent too
                            par.set_status(self.STA_ACTIVE)
                # We dont mark the children as Active because
                # They might be already completed after all

        # then the task itself
        if status:
            self.status = status

        # Set closing date
        if status and status in [self.STA_DONE, self.STA_DISMISSED]:
            # to the specified date (if any)
            if donedate:
                self.closed_date = donedate
            # or to today
            else:
                self.closed_date = Date.today()
        self.sync()
Exemplo n.º 15
0
    def set_status(self, status, donedate=None, propagation=False):
        old_status = self.status
        self.can_be_deleted = False
        # No need to update children or whatever if the task is not loaded
        if status and self.is_loaded():
            # we first modify the status of the children
            # If Done, we set the done date
            if status in [self.STA_DONE, self.STA_DISMISSED]:
                for c in self.get_subtasks():
                    if c.get_status() in [self.STA_ACTIVE]:
                        c.set_status(status,
                                     donedate=donedate,
                                     propagation=True)

                # If the task is recurring, it must be duplicate with
                # another task id and the next occurence of the task
                # while preserving child/parent relations.
                # For a task to be duplicated, it must satisfy 3 rules.
                #   1- It is recurring.
                #   2- It has no parent or no recurring parent.
                #   3- It was directly marked as done (not by propagation from its parent).
                rules = [self.recurring, not propagation]
                if all(rules) and not self.is_parent_recurring():
                    # duplicate all the children
                    nexttask_tid = self.duplicate_recursively()
                    if self.has_parent():
                        for p_tid in self.get_parents():
                            par = self.req.get_task(p_tid)
                            if (par.is_loaded()
                                    and par.get_status() in (self.STA_ACTIVE)):
                                par.add_child(nexttask_tid)

                                par.sync()

            # If we mark a task as Active and that some parent are not
            # Active, we break the parent/child relation
            # It has no sense to have an active subtask of a done parent.
            # (old_status check is necessary to avoid false positive a start)
            elif status in [self.STA_ACTIVE] and\
                    old_status in [self.STA_DONE, self.STA_DISMISSED]:
                if self.has_parent():
                    for p_tid in self.get_parents():
                        par = self.req.get_task(p_tid)
                        if par.is_loaded() and par.get_status() in\
                                [self.STA_DONE, self.STA_DISMISSED]:
                            # we can either break the parent/child relationship
                            # self.remove_parent(p_tid)
                            # or restore the parent too
                            par.set_status(self.STA_ACTIVE)
                # We dont mark the children as Active because
                # They might be already completed after all

        # then the task itself
        if status:
            self.status = status

        # Set closing date
        if status and status in [self.STA_DONE, self.STA_DISMISSED]:
            # to the specified date (if any)
            if donedate:
                self.closed_date = donedate
            # or to today
            else:
                self.closed_date = Date.today()
        self.sync()
Exemplo n.º 16
0
# TIDs for each initial task
task_ids = [
    '33d9760d-2e07-4ae4-9c02-1fcdaeb46325',
    '262d1410-71aa-4e35-abec-90ef1bab44d3',
    '2d427a77-3077-4277-904c-073fcfcb4842',
    '0653765c-f5d4-4b7f-9903-0065bb258940',
    '3efd01b7-343d-4be1-9bac-8ed944517e3f',
    '9b05a6c5-81e3-4fe5-a70d-3b569f65409c',
    '21ed54a8-e7ff-408a-b648-8d12cc75e162',
    'd1c73224-9eec-4889-b3c4-bb0281e5c1d3',
    '586dd1a7-0772-4d0a-85db-34edfc8ee30c',
]

# Date for modified and added tags
today = str(Date.today())

# Initial tasks
# flake8: noqa: E501
tasks = [
    {
        'title':
        _("Getting Started with GTG (read me first)"),
        'id':
        task_ids[0],
        'subtasks':
        task_ids[1:],
        'added':
        today,
        'modified':
        today,
Exemplo n.º 17
0
# TIDs for each initial task
task_ids = [
    '33d9760d-2e07-4ae4-9c02-1fcdaeb46325',
    '262d1410-71aa-4e35-abec-90ef1bab44d3',
    '2d427a77-3077-4277-904c-073fcfcb4842',
    '0653765c-f5d4-4b7f-9903-0065bb258940',
    '3efd01b7-343d-4be1-9bac-8ed944517e3f',
    '9b05a6c5-81e3-4fe5-a70d-3b569f65409c',
    '21ed54a8-e7ff-408a-b648-8d12cc75e162',
    'd1c73224-9eec-4889-b3c4-bb0281e5c1d3',
    '586dd1a7-0772-4d0a-85db-34edfc8ee30c',
]

# Date for modified and added tags
today = Date.today().xml_str()

# Initial tasks
# flake8: noqa: E501
tasks = [
    {
        'title':
        _("Getting Started with GTG (read me first)"),
        'id':
        task_ids[0],
        'subtasks':
        task_ids[1:],
        'added':
        today,
        'modified':
        today,
Exemplo n.º 18
0
    def get_node_bgcolor(self, node):
        """ This method checks the urgency of a node (task) and returns its
         urgency background color"""
        sdate = node.get_start_date()
        ddate = node.get_due_date()
        daysleft = ddate.days_left()

        # Dates undefined
        if (ddate == Date.today()):
            return self._get_color(2)  # High urgency
        elif ddate != Date.no_date():  # Has a due date
            if daysleft < 0:
                return self._get_color(3)  # Overdue
            elif (sdate == Date.no_date() and  # Has no start date
                  not ddate.is_fuzzy()):  # Due date is not fuzzy
                return self._get_color(1)  # Normal

        # Fuzzy dates (now, soon, someday)
        # These can ignore the start date
        if (ddate == Date.now()):
            return self._get_color(2)
        elif (ddate == Date.soon()):
            return self._get_color(1)
        elif (ddate == Date.someday()):
            return self._get_color(0)

        # Dates fully defined. Calculate gradient color
        elif (sdate != Date.no_date() != ddate):
            dayspan = (ddate - sdate).days
            redf = self._pref_data['reddays']
            reddays = int(ceil(redf / 100.0 * dayspan))

            # Gradient variables
            grad_dayspan = dayspan - reddays
            grad_half_dayspan = grad_dayspan / 2.0

            # Default to low urgency color
            color = self._get_color(0)

            # CL : low urgency color
            # CN : normal urgency color
            # CH : high urgency color
            # CO : overdue color
            # To understand this section, it is easier to draw out a
            # timeline divided into 3 sections: CL to CN, CN to CH and
            # the reddays section. Then point out the spans of the
            # different variables (dayspan, daysleft, reddays,
            # grad_dayspan, grad_half_dayspan)
            if daysleft < 0:  # CO
                color = self._get_color(3)
            elif daysleft <= reddays:  # CH
                color = self._get_color(2)
            elif daysleft <= (dayspan - grad_half_dayspan):
                # Gradient CN to CH
                # Has to be float so division by it is non-zero
                steps = float(grad_half_dayspan)
                step = grad_half_dayspan - (daysleft - reddays)
                color = self._get_gradient_color(self._get_color(1),
                                                 self._get_color(2),
                                                 step / steps)
            elif daysleft <= dayspan:
                # Gradient CL to CN
                steps = float(grad_half_dayspan)
                step = grad_half_dayspan - (daysleft - reddays -
                                            grad_half_dayspan)
                color = self._get_gradient_color(self._get_color(0),
                                                 self._get_color(1),
                                                 step / steps)

            return color

        # Insufficient data to determine urgency
        else:
            return None