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
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
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()
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())
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())
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()
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())
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()
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)
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()
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()
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
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())]
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()
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()
# 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,
# 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,
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