def test_parse_local_fuzzy_dates(self): """ Parse fuzzy dates in their localized version """ self.assertEqual(Date.parse(_("now")), Date.now()) self.assertEqual(Date.parse(_("soon")), Date.soon()) self.assertEqual(Date.parse(_("later")), Date.someday()) self.assertEqual(Date.parse(_("someday")), Date.someday()) self.assertEqual(Date.parse(""), Date.no_date())
def test_parse_fuzzy_dates_str(self): """ Print fuzzy dates in localized version """ self.assertEqual(str(Date.parse("now")), _("now")) self.assertEqual(str(Date.parse("soon")), _("soon")) self.assertEqual(str(Date.parse("later")), _("someday")) self.assertEqual(str(Date.parse("someday")), _("someday")) self.assertEqual(str(Date.parse("")), "")
def test_parse_fuzzy_dates(self): """ Parse fuzzy dates like now, soon, later, someday """ self.assertEqual(Date.parse("now"), Date.now()) self.assertEqual(Date.parse("soon"), Date.soon()) self.assertEqual(Date.parse("later"), Date.someday()) self.assertEqual(Date.parse("someday"), Date.someday()) self.assertEqual(Date.parse(""), Date.no_date())
def test_due(self): expected1 = { 'title': 'Do a thing', 'tags': set(), 'start': None, 'due': Date.parse('monday'), 'recurring': None } expected2 = { 'title': 'Do a thing', 'tags': set(), 'start': None, 'due': Date.parse('someday'), 'recurring': None } expected3 = { 'title': 'Do a thing', 'tags': set(), 'start': None, 'due': Date.parse('2099/02/12'), 'recurring': None } text1 = 'Do a thing due:monday' text2 = 'Do a thing due:monday' text3 = 'Do a thing due: monday' text4 = 'Do a thing due: someday' text5 = 'Do a thing due: 2099/02/12' self.assertEqual(expected1, parse(text1)) self.assertEqual(expected1, parse(text2)) self.assertEqual(expected1, parse(text3)) self.assertEqual(expected3, parse(text5))
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 set_complex_title(self, text, tags=[]): if tags: assert (isinstance(tags[0], str)) due_date = Date.no_date() defer_date = Date.no_date() if text: # Get tags in the title for match in extract_tags_from_text(text): tags.append(match) # Get attributes regexp = r'([\s]*)([\w-]+):\s*([^\s]+)' matches = re.findall(regexp, text, re.UNICODE) for spaces, attribute, args in matches: valid_attribute = True if attribute.lower() in ["tags", _("tags"), "tag", _("tag")]: for tag in args.split(","): if not tag.strip() == "@" and not tag.strip() == "": if not tag.startswith("@"): tag = "@" + tag tags.append(tag) elif attribute.lower() in [ "defer", _("defer"), "start", _("start") ]: try: defer_date = Date.parse(args) except ValueError: valid_attribute = False elif attribute.lower() == "due" or \ attribute.lower() == _("due"): try: due_date = Date.parse(args) except: valid_attribute = False else: # attribute is unknown valid_attribute = False if valid_attribute: # remove valid attribute from the task title text = \ text.replace(f"{spaces}{attribute}:{args}", "") for t in tags: self.add_tag(t) if text != "": self.set_title(text.strip()) self.set_to_keep() self.set_due_date(due_date) self.set_start_date(defer_date)
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 date_focus_out(self, widget, event, date_kind): try: datetoset = Date.parse(widget.get_text()) except ValueError: datetoset = None if datetoset is not None: # TODO: New Core t = self.app.ds.tasks.get(self.task.tid) if date_kind == GTGCalendar.DATE_KIND_START: self.task.set_start_date(datetoset) t.date_start = datetoset self.start_popover.popdown() elif date_kind == GTGCalendar.DATE_KIND_DUE: self.task.set_due_date(datetoset) t.date_due = datetoset self.due_popover.popdown() elif date_kind == GTGCalendar.DATE_KIND_CLOSED: self.task.set_closed_date(datetoset) t.date_closed = datetoset self.closed_popover.popdown() self.refresh_editor()
def test_missing_year_next_year(self): """ Parsing %m%d have to find correct date: we enter a day the next year """ aday = date.today() if aday.day == 1 and aday.month == 1: # not possible to add a day next year return aday = aday.replace(year=aday.year + 1, month=1, day=1) self.assertEqual(Date.parse("0101"), aday)
def test_on_certain_day(self): """ Parse due:3 as 3rd day this month or next month if it is already more or already 3rd day """ for i in range(28): i += 1 aday = date.today() if i <= aday.day: aday = next_month(aday, i) else: aday = aday.replace(day=i) self.assertEqual(Date.parse(str(i)), aday)
def date_changed(self, widget, data): try: Date.parse(widget.get_text()) valid = True except ValueError: valid = False if valid: # If the date is valid, we write with default color in the widget # "none" will set the default color. widget.override_color(Gtk.StateType.NORMAL, None) widget.override_background_color(Gtk.StateType.NORMAL, None) else: # We should write in red in the entry if the date is not valid text_color = Gdk.RGBA() text_color.parse("#F00") widget.override_color(Gtk.StateType.NORMAL, text_color) bg_color = Gdk.RGBA() bg_color.parse("#F88") widget.override_background_color(Gtk.StateType.NORMAL, bg_color)
def ModifyTask(self, tid, task_data): """ Updates the task with ID tid using the provided information in the task_data structure. Note that any fields left blank or undefined in task_data will clear the value in the task, so the best way to update a task is to first retrieve it via get_task(tid), modify entries as desired, and send it back via this function. """ task = self.req.get_task(tid) task.set_status(task_data["status"], donedate=Date.parse(task_data["donedate"])) task.set_title(task_data["title"]) task.set_due_date(Date.parse(task_data["duedate"])) task.set_start_date(Date.parse(task_data["startdate"])) task.set_text(task_data["text"]) for tag in task_data["tags"]: task.add_tag(tag) for sub in task_data["subtask"]: task.add_child(sub) return task_to_dict(task)
def NewTask(self, status, title, duedate, startdate, donedate, tags, text, subtasks): """ Generate a new task object and return the task data as a dict @param status: One of 'Active', 'Dismiss', or 'Done' @param title: String name of the task @param duedate: Date the task is due, such as "2010-05-01". also supports 'now', 'soon', 'someday' @param startdate: Date the task will be started @param donedate: Date the task was finished @param tags: List of strings for tags to apply for this task @param text: String description @param subtasks: A list of task ids of tasks to add as children @return: A dictionary with the data of the newly created task """ nt = self.req.new_task(tags=tags) for sub in subtasks: nt.add_child(sub) nt.set_status(status, donedate=Date.parse(donedate)) nt.set_title(title) nt.set_due_date(Date.parse(duedate)) nt.set_start_date(Date.parse(startdate)) nt.set_text(text) return task_to_dict(nt)
def test_parse_week_days(self): """ Parse name of week days and don't care about case-sensitivity """ weekday = date.today().weekday() for i, day in enumerate(['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']): if i <= weekday: expected = date.today() + timedelta(7 + i - weekday) else: expected = date.today() + timedelta(i - weekday) self.assertEqual(Date.parse(day), expected) self.assertEqual(Date.parse(day.lower()), expected) self.assertEqual(Date.parse(day.upper()), expected) # Test localized version day = _(day) self.assertEqual(Date.parse(day), expected) self.assertEqual(Date.parse(day.lower()), expected) self.assertEqual(Date.parse(day.upper()), expected)
def task_from_xml(task, xmlnode): # print "********************************" # print xmlnode.toprettyxml() task.set_uuid(xmlnode.getAttribute("uuid")) task.set_title(read_node(xmlnode, "title")) status = xmlnode.getAttribute("status") donedate = Date.parse(read_node(xmlnode, "donedate")) task.set_status(status, donedate=donedate) duedate = Date(read_node(xmlnode, "duedate")) task.set_due_date(duedate) startdate = Date(read_node(xmlnode, "startdate")) task.set_start_date(startdate) modified = read_node(xmlnode, "modified") if modified != "": modified = datetime.strptime(modified, "%Y-%m-%dT%H:%M:%S") task.set_modified(modified) added = read_node(xmlnode, "addeddate") if added: added = datetime.strptime(added, "%Y-%m-%dT%H:%M:%S") task.set_added_date(added) tags = xmlnode.getAttribute("tags").replace(' ', '') tags = (tag for tag in tags.split(',') if tag.strip() != "") for tag in tags: # FIXME why unescape???? task.tag_added(saxutils.unescape(tag)) # FIXME why we need to convert that through an XML? content = read_node(xmlnode, "content") if content != "": content = f"<content>{content}</content>" content = minidom.parseString(content).firstChild.toxml() task.set_text(content) for subtask in xmlnode.getElementsByTagName("subtask"): task.add_child(get_text(subtask)) for attr in xmlnode.getElementsByTagName("attribute"): if len(attr.childNodes) > 0: value = get_text(attr) else: value = "" key = attr.getAttribute("key") namespace = attr.getAttribute("namespace") task.set_attribute(key, value, namespace=namespace) # FIXME do we need remote task ids? I don't think so # FIXME if so => rework them into a more usable structure!!! # (like attributes) # REMOTE TASK IDS """ remote_ids_list = xmlnode.getElementsByTagName("task-remote-ids") for remote_id in remote_ids_list: if remote_id.childNodes: node = remote_id.childNodes[0] backend_id = node.firstChild.nodeValue remote_task_id = node.childNodes[1].firstChild.nodeValue task.add_remote_id(backend_id, remote_task_id) """ return task
def task_from_element(task, element: etree.Element): """Populate task from XML element.""" task.set_title(element.find('title').text) task.set_uuid(element.get('id')) dates = element.find('dates') modified = dates.find('modified').text task.set_modified(datetime.fromisoformat(modified)) added = dates.find('added').text task.set_added_date(datetime.fromisoformat(added)) # Dates try: done_date = Date.parse(dates.find('done').text) task.set_status(element.attrib['status'], donedate=done_date) except AttributeError: pass try: due_date = Date.parse(dates.find('due').text) task.set_due_date(due_date) except AttributeError: pass try: start = dates.find('start').text task.set_start_date(start) except (AttributeError, TypeError): pass # Recurring tasks recurring = element.find('recurring') recurring_enabled = recurring.get('enabled') try: recurring_term = element.find('term').text task.set_recurring(recurring_enabled == 'true', None if recurring_term == 'None' else recurring_term) except AttributeError: pass taglist = element.find('tags') if taglist is not None: [task.tag_added_by_id(t.text) for t in taglist.iter('tag')] # Content content = element.find('content').text or '' content = content.replace(']]>', ']]>') task.set_text(content) # Subtasks subtasks = element.find('subtasks') for sub in subtasks.findall('sub'): task.add_child(sub.text) return task
def refresh_editor(self, title=None, refreshtext=False): if self.window is None: return to_save = False # title of the window if title: self.window.set_title(title) to_save = True else: self.window.set_title(self.task.get_title()) status = self.task.get_status() if status == Task.STA_DISMISSED: self.donebutton.show() self.undonebutton.hide() self.dismissbutton.hide() self.undismissbutton.show() elif status == Task.STA_DONE: self.donebutton.hide() self.undonebutton.show() self.dismissbutton.show() self.undismissbutton.hide else: self.donebutton.show() self.undonebutton.hide() self.dismissbutton.show() self.undismissbutton.hide() # Refreshing the parent button if self.task.has_parent(): # Translators: Button label to open the parent task self.parent_button.set_label(_('Open Parent')) else: # Translators: Button label to add an new parent task self.parent_button.set_label(_('Add Parent')) # Refreshing the status bar labels and date boxes if status in [Task.STA_DISMISSED, Task.STA_DONE]: self.builder.get_object("start_box").hide() self.builder.get_object("closed_box").show() else: self.builder.get_object("closed_box").hide() self.builder.get_object("start_box").show() # refreshing the start date field startdate = self.task.get_start_date() try: prevdate = Date.parse(self.start_entry.get_text()) update_date = startdate != prevdate except ValueError: update_date = True if update_date: self.start_entry.set_text(str(startdate)) # refreshing the due date field duedate = self.task.get_due_date() try: prevdate = Date.parse(self.due_entry.get_text()) update_date = duedate != prevdate except ValueError: update_date = True if update_date: self.due_entry.set_text(str(duedate)) # refreshing the closed date field closeddate = self.task.get_closed_date() prevcldate = Date.parse(self.closed_entry.get_text()) if closeddate != prevcldate: self.closed_entry.set_text(str(closeddate)) # refreshing the day left label """ TODO(jakubbrindza): re-enable refreshing the day left. We need to come up how and where this information is viewed in the editor window. """ # self.refresh_day_left() if refreshtext: self.textview.modified(refresheditor=False) if to_save: self.light_save()
def test_missing_year_this_year(self): """ Parsing %m%d have to find correct date: we enter a day this year """ aday = next_month(date.today(), day=1) parse_string = "%02d%02d" % (aday.month, aday.day) self.assertEqual(Date.parse(parse_string), aday)
def test_parses_todays_month_day_format(self): today = date.today() parse_string = "%02d%02d" % (today.month, today.day) self.assertEqual(Date.parse(parse_string), today)
def test_parses_common_formats(self): self.assertEqual(str(Date.parse("1985-03-29")), "1985-03-29") self.assertEqual(str(Date.parse("19850329")), "1985-03-29") self.assertEqual(str(Date.parse("1985/03/29")), "1985-03-29")
def parse_search_query(query): """ Parse query into parameters for search filter If query is not correct, exception InvalidQuery is raised. """ if len(query.strip()) == 0: raise InvalidQuery("Query is empty") if query.count('"') % 2 != 0: raise InvalidQuery("Query has odd number of quotes") commands = [] not_count, after_or = 0, False require_date = None for token, value in _tokenize_query(query): cmd = None if require_date: if token not in ['date', 'word', 'literal']: raise InvalidQuery( f"Unexpected token '{token}' after '{require_date}'") value = value.strip('"') try: date = Date.parse(value) except ValueError: raise InvalidQuery(f"Date '{value}' in wrong format") cmd = (require_date, not_count % 2 == 0, date) require_date = None elif token == 'command': value = value.lower()[1:] found = False for keyword in KEYWORDS: if value not in KEYWORDS[keyword]: continue if keyword == 'not': not_count += 1 elif keyword == 'or': if not_count > 0: raise InvalidQuery("!or cann't follow !not") if commands == []: raise InvalidQuery( "Or is not allowed at the beginning of query") if commands[-1][0] != "or": commands.append(("or", True, [commands.pop()])) after_or = True elif keyword in ['after', 'before']: require_date = keyword else: cmd = (keyword, not_count % 2 == 0) found = True break if not found: raise InvalidQuery(f"Unknown command !{value}") elif token == 'tag': cmd = (token, not_count % 2 == 0, value) elif token in ['literal', 'word']: cmd = ('word', not_count % 2 == 0, value.strip('"').lower()) if cmd is not None: if after_or: commands[-1][2].append(cmd) else: commands.append(cmd) not_count, after_or = 0, False if not_count > 0: raise InvalidQuery("Query cannot end with !not (Forgot something?)") if after_or: raise InvalidQuery("Or is not allowed at the end of query") if require_date: raise InvalidQuery(f"Required date after '{require_date}'") return {'q': commands}
def from_xml(self, xml: Element, tag_store: TagStore) -> None: """Load up tasks from a lxml object.""" elements = list(xml.iter(self.XML_TAG)) for element in elements: tid = element.get('id') title = element.find('title').text status = element.get('status') task = Task2(id=tid, title=title) dates = element.find('dates') modified = dates.find('modified').text task.date_modified = Date( datetime.datetime.fromisoformat(modified)) added = dates.find('added').text task.date_added = Date(datetime.datetime.fromisoformat(added)) if status == 'Done': task.status = Status.DONE elif status == 'Dismissed': task.status = Status.DISMISSED # Dates try: closed = Date.parse(dates.find('done').text) task.date_closed = closed except AttributeError: pass fuzzy_due_date = Date.parse(dates.findtext('fuzzyDue')) due_date = Date.parse(dates.findtext('due')) if fuzzy_due_date: task._date_due = fuzzy_due_date elif due_date: task._date_due = due_date fuzzy_start = dates.findtext('fuzzyStart') start = dates.findtext('start') if fuzzy_start: task.date_start = Date(fuzzy_start) elif start: task.date_start = Date(start) taglist = element.find('tags') if taglist is not None: for t in taglist.iter('tag'): try: tag = tag_store.get(t.text) task.tags.append(tag) except KeyError: pass # Content content = element.find('content').text or '' content = content.replace(']]>', ']]>') task.content = content self.add(task) log.debug('Added %s', task) # All tasks have been added, now we parent them for element in elements: parent_tid = element.get('id') subtasks = element.find('subtasks') for sub in subtasks.findall('sub'): self.parent(sub.text, parent_tid)
def task_from_element(task, element: etree.Element): """Populate task from XML element.""" task.set_uuid(element.get('uuid')) task.set_title(element.find('title').text) # Dates try: done_date = Date.parse(element.find('donedate').text) task.set_status(element.attrib['status'], donedate=done_date) except AttributeError: pass try: due_date = Date.parse(element.find('duedate').text) task.set_due_date(due_date) except AttributeError: pass try: modified = element.find('modified').text modified = datetime.strptime(modified, "%Y-%m-%dT%H:%M:%S") task.set_modified(modified) except AttributeError: pass try: added = element.find('addeddate').text added = datetime.strptime(added, "%Y-%m-%dT%H:%M:%S") task.set_added_date(added) except AttributeError: pass try: start = element.find('startdate').text task.set_start_date(start) except (AttributeError, TypeError): pass # Task Tags tags = element.get('tags') tags = [t for t in tags.split(',') if t.strip() != ''] [task.tag_added(t) for t in tags] try: content = element.find('content').text # Content includes serialized xml, so it needs to be re-serialized # and then deserialized again. if content: content = f"<content>{content}</content>" task.set_text(content) except AttributeError: # Some tasks might not have a content tag pass # Subtasks [task.add_child(sub.text) for sub in element.findall('subtask')] return task