diff --git a/app/src/androidTest/java/com/todoroo/astrid/service/QuickAddMarkupTest.kt b/app/src/androidTest/java/com/todoroo/astrid/service/QuickAddMarkupTest.kt index a968bb78f..b6984c6d5 100644 --- a/app/src/androidTest/java/com/todoroo/astrid/service/QuickAddMarkupTest.kt +++ b/app/src/androidTest/java/com/todoroo/astrid/service/QuickAddMarkupTest.kt @@ -85,7 +85,7 @@ class QuickAddMarkupTest : InjectingTestCase() { task = Task() task!!.title = title tags.clear() - TitleParser.parse(tagDataDao, task, tags) + TitleParser.parse(tagDataDao, task!!, tags) } private fun assertPriority(priority: Int) { diff --git a/app/src/main/java/com/todoroo/astrid/utility/TitleParser.java b/app/src/main/java/com/todoroo/astrid/utility/TitleParser.java deleted file mode 100644 index 2cd5b7f7f..000000000 --- a/app/src/main/java/com/todoroo/astrid/utility/TitleParser.java +++ /dev/null @@ -1,479 +0,0 @@ -/* - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ - -package com.todoroo.astrid.utility; - -import static org.tasks.Strings.isNullOrEmpty; - -import com.google.ical.values.Frequency; -import com.google.ical.values.RRule; -import com.mdimension.jchronic.AstridChronic; -import com.mdimension.jchronic.Chronic; -import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.data.Task.Priority; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.tasks.data.TagDataDaoBlocking; -import timber.log.Timber; - -public class TitleParser { - - public static void parse(TagDataDaoBlocking tagDataDao, Task task, ArrayList tags) { - repeatHelper(task); - listHelper( - tagDataDao, - task, - tags); // Don't need to know if tags affected things since we don't show alerts for them - dayHelper(task); - priorityHelper(task); - } - - public static String trimParenthesis(String pattern) { - if (pattern.charAt(0) == '#' || pattern.charAt(0) == '@') { - pattern = pattern.substring(1); - } - if ('(' == pattern.charAt(0)) { - return pattern.substring(1, pattern.length() - 1); - } - return pattern; - } - - public static void listHelper(TagDataDaoBlocking tagDataDao, Task task, ArrayList tags) { - String inputText = task.getTitle(); - Pattern tagPattern = Pattern.compile("(\\s|^)#(\\(.*\\)|[^\\s]+)"); - Pattern contextPattern = Pattern.compile("(\\s|^)@(\\(.*\\)|[^\\s]+)"); - - Set addedTags = new HashSet<>(); - - while (true) { - Matcher m = tagPattern.matcher(inputText); - if (m.find()) { - String tag = TitleParser.trimParenthesis(m.group(2)); - String tagWithCase = tagDataDao.getTagWithCase(tag); - if (!addedTags.contains(tagWithCase)) { - tags.add(tagWithCase); - } - addedTags.add(tagWithCase); - } else { - m = contextPattern.matcher(inputText); - if (m.find()) { - String tag = TitleParser.trimParenthesis(m.group(2)); - String tagWithCase = tagDataDao.getTagWithCase(tag); - if (!addedTags.contains(tagWithCase)) { - tags.add(tagWithCase); - } - addedTags.add(tagWithCase); - } else { - break; - } - } - inputText = inputText.substring(0, m.start()) + inputText.substring(m.end()); - } - task.setTitle(inputText.trim()); - } - - private static int strToPriority(String priorityStr) { - if (priorityStr != null) { - priorityStr.toLowerCase().trim(); - } - int priority = Priority.HIGH; - if ("0".equals(priorityStr) - || "!0".equals(priorityStr) - || "least".equals(priorityStr) - || "lowest".equals(priorityStr)) { - priority = Priority.NONE; - } - if ("!".equals(priorityStr) - || "!1".equals(priorityStr) - || "bang".equals(priorityStr) - || "1".equals(priorityStr) - || "low".equals(priorityStr)) { - priority = Priority.LOW; - } - if ("!!".equals(priorityStr) - || "!2".equals(priorityStr) - || "bang bang".equals(priorityStr) - || "2".equals(priorityStr) - || "high".equals(priorityStr)) { - priority = Priority.MEDIUM; - } - return priority; - } - - // priorityHelper parses the string and sets the Task's importance - private static void priorityHelper(Task task) { - String inputText = task.getTitle(); - String[] importanceStrings = { - "()((^|[^\\w!])!+|(^|[^\\w!])!\\d)($|[^\\w!])", - "()(?i)((\\s?bang){1,})$", - "(?i)(\\spriority\\s?(\\d)$)", - "(?i)(\\sbang\\s?(\\d)$)", - "(?i)()(\\shigh(est)?|\\slow(est)?|\\stop|\\sleast) ?priority$" - }; - for (String importanceString : importanceStrings) { - Pattern importancePattern = Pattern.compile(importanceString); - while (true) { - Matcher m = importancePattern.matcher(inputText); - if (m.find()) { - task.setPriority(strToPriority(m.group(2).trim())); - int start = m.start() == 0 ? 0 : m.start() + 1; - inputText = inputText.substring(0, start) + inputText.substring(m.end()); - - } else { - break; - } - } - } - task.setTitle(inputText.trim()); - } - - // helper for dayHelper. Converts am/pm to an int 0/1. - private static int ampmToNumber(String amPmString) { - int time = Calendar.PM; - if (amPmString == null) { - return time; - } - String text = amPmString.toLowerCase().trim(); - if (text.equals("am") || text.equals("a.m") || text.equals("a")) { - time = Calendar.AM; - } - if (text.equals("pm") || text.equals("p.m") || text.equals("p")) { - time = Calendar.PM; - } - return time; - } - - private static String removeIfParenthetical(Matcher m, String inputText) { - String s = m.group(); - if (s.startsWith("(") && s.endsWith(")")) { - return inputText.substring(0, m.start()) + inputText.substring(m.end()); - } - return inputText; - } - - private static String stripParens(String s) { - if (s.startsWith("(")) { - s = s.substring(1); - } - if (s.endsWith(")")) { - s = s.substring(0, s.length() - 1); - } - return s; - } - - // ---------------------DATE-------------------------- - // Handles setting the task's date. - // Day of week (e.g. Monday, Tuesday,..) is overridden by a set date (e.g. October 23 2013). - // Vague times (e.g. breakfast, night) are overridden by a set time (9 am, at 10, 17:00) - private static void dayHelper(Task task) { - String inputText = task.getTitle(); - Calendar cal = null; - boolean containsSpecificTime = false; - String[] daysOfWeek = { - "(?i)(\\(|\\b)today(\\)|\\b)", - "(?i)(\\(|\\b)tomorrow(\\)|\\b)", - "(?i)(\\(|\\b)mon(day(\\)|\\b)|(\\)|\\.))", - "(?i)(\\(|\\b)tue(sday(\\)|\\b)|(\\)|\\.))", - "(?i)(\\(|\\b)wed(nesday(\\)|\\b)|(\\)|\\.))", - "(?i)(\\(|\\b)thu(rsday(\\)|\\b)|(\\)|\\.))", - "(?i)(\\(|\\b)fri(day(\\)|\\b)|(\\)|\\.))", - "(?i)(\\(|\\b)sat(urday(\\)|\\b)|(\\)|\\.))", - "(?i)(\\(|\\b)sun(day(\\)|\\b)|(\\)|\\.))" - }; - - for (String date : daysOfWeek) { - Pattern pattern = Pattern.compile(date); - Matcher m = pattern.matcher(inputText); - if (m.find()) { - String toParse = stripParens(m.group(0)); - cal = AstridChronic.parse(toParse).getBeginCalendar(); - inputText = removeIfParenthetical(m, inputText); - // then put it into task - } - } - - String[] dates = { - "(?i)(\\(|\\b)(jan(\\.|uary))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", - "(?i)(\\(|\\b)(feb(\\.|ruary))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", - "(?i)(\\(|\\b)(mar(\\.|ch))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", - "(?i)(\\(|\\b)(apr(\\.|il))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", - "(?i)(\\(|\\b)(may())(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", - "(?i)(\\(|\\b)(jun(\\.|e))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", - "(?i)(\\(|\\b)(jul(\\.|y))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", - "(?i)(\\(|\\b)(aug(\\.|ust))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", - "(?i)(\\(|\\b)(sep(\\.|tember))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", - "(?i)(\\(|\\b)(oct(\\.|ober))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", - "(?i)(\\(|\\b)(nov(\\.|ember))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", - "(?i)(\\(|\\b)(dec(\\.|ember))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)" - }; - - // m.group(2) = "month" - // m.group(5) = "day" - for (String date : dates) { - Pattern pattern = Pattern.compile(date); - Matcher m = pattern.matcher(inputText); - - if (m.find()) { - Calendar dateCal = Chronic.parse(m.group(2)).getBeginCalendar(); - if (m.group(5) != null) { - dateCal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(m.group(5))); - } - Calendar today = Calendar.getInstance(); - if (m.group(6) != null) { - dateCal.set(Calendar.YEAR, Integer.parseInt(m.group(6).trim())); - } else if (today.get(Calendar.MONTH) - dateCal.get(Calendar.MONTH) - > 1) { // if more than a month in the past - dateCal.set(Calendar.YEAR, dateCal.get(Calendar.YEAR) + 1); - } - if (cal == null) { - cal = dateCal; - } else { - cal.set(Calendar.DAY_OF_MONTH, dateCal.get(Calendar.DAY_OF_MONTH)); - cal.set(Calendar.MONTH, dateCal.get(Calendar.MONTH)); - cal.set(Calendar.YEAR, dateCal.get(Calendar.YEAR)); - } - inputText = removeIfParenthetical(m, inputText); - } - } - - // for dates in the format MM/DD - Pattern p = - Pattern.compile( - "(?i)(\\(|\\b)(1[0-2]|0?[1-9])(\\/|-)(3[0-1]|[0-2]?[0-9])(\\/|-)?(\\d{4}|\\d{2})?(\\)|\\b)"); - Matcher match = p.matcher(inputText); - if (match.find()) { - Calendar dCal = Calendar.getInstance(); - setCalendarToDefaultTime(dCal); - dCal.set(Calendar.MONTH, Integer.parseInt(match.group(2).trim()) - 1); - dCal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(match.group(4))); - if (match.group(6) != null && !(match.group(6).trim()).equals("")) { - String yearString = match.group(6); - if (match.group(6).length() == 2) { - yearString = "20" + match.group(6); - } - dCal.set(Calendar.YEAR, Integer.parseInt(yearString)); - } - - if (cal == null) { - cal = dCal; - } else { - cal.set(Calendar.DAY_OF_MONTH, dCal.get(Calendar.DAY_OF_MONTH)); - cal.set(Calendar.MONTH, dCal.get(Calendar.MONTH)); - cal.set(Calendar.YEAR, dCal.get(Calendar.YEAR)); - } - inputText = removeIfParenthetical(match, inputText); - } - - HashMap dayTimes = new HashMap<>(); - dayTimes.put("(?i)\\bbreakfast\\b", 8); - dayTimes.put("(?i)\\blunch\\b", 12); - dayTimes.put("(?i)\\bsupper\\b", 18); - dayTimes.put("(?i)\\bdinner\\b", 18); - dayTimes.put("(?i)\\bbrunch\\b", 10); - dayTimes.put("(?i)\\bmorning\\b", 8); - dayTimes.put("(?i)\\bafternoon\\b", 15); - dayTimes.put("(?i)\\bevening\\b", 19); - dayTimes.put("(?i)\\bnight\\b", 19); - dayTimes.put("(?i)\\bmidnight\\b", 0); - dayTimes.put("(?i)\\bnoon\\b", 12); - - Set keys = dayTimes.keySet(); - for (String dayTime : keys) { - Pattern pattern = Pattern.compile(dayTime); - Matcher m = pattern.matcher(inputText); - if (m.find()) { - containsSpecificTime = true; - int timeHour = dayTimes.get(dayTime); - Calendar dayTimesCal = Calendar.getInstance(); - setCalendarToDefaultTime(dayTimesCal); - dayTimesCal.set(Calendar.HOUR, timeHour); - if (cal == null) { - cal = dayTimesCal; - } else { - setCalendarToDefaultTime(cal); - cal.set(Calendar.HOUR, timeHour); - } - } - } - - String[] times = { - // [time] am/pm - "(?i)(\\b)([01]?\\d):?([0-5]\\d)? ?([ap]\\.?m?\\.?)\\b", - // army time - "(?i)\\b(([0-2]?[0-9]):([0-5][0-9]))(\\b)", - // [int] o'clock - "(?i)\\b(([01]?\\d)() ?o'? ?clock) ?([ap]\\.?m\\.?)?\\b", - // at [int] - "(?i)(\\bat) ([01]?\\d)()($|\\D($|\\D))" - - // m.group(2) holds the hour - // m.group(3) holds the minutes - // m.group(4) holds am/pm - }; - - for (String time : times) { - Pattern pattern = Pattern.compile(time); - Matcher m = pattern.matcher(inputText); - if (m.find()) { - containsSpecificTime = true; - Calendar today = Calendar.getInstance(); - Calendar timeCal = Calendar.getInstance(); - setCalendarToDefaultTime(timeCal); - timeCal.set(Calendar.HOUR, Integer.parseInt(m.group(2))); - - if (m.group(3) != null && !m.group(3).trim().equals("")) { - timeCal.set(Calendar.MINUTE, Integer.parseInt(m.group(3))); - } else { - timeCal.set(Calendar.MINUTE, 0); - } - if (Integer.parseInt(m.group(2)) <= 12) { - timeCal.set(Calendar.AM_PM, ampmToNumber(m.group(4))); - } - - // sets it to the next occurrence of that hour if no am/pm is provided. doesn't include - // military time - if (Integer.parseInt(m.group(2)) <= 12 - && (m.group(4) == null || (m.group(4).trim()).equals(""))) { - while (timeCal.getTime().getTime() < today.getTime().getTime()) { - timeCal.set(Calendar.HOUR_OF_DAY, timeCal.get(Calendar.HOUR_OF_DAY) + 12); - } - } else { // if am/pm is provided and the time is in the past, set it to the next day. - // Military time included. - if (timeCal.get(Calendar.HOUR) != 0 - && (timeCal.getTime().getTime() < today.getTime().getTime())) { - timeCal.set(Calendar.DAY_OF_MONTH, timeCal.get(Calendar.DAY_OF_MONTH) + 1); - } - if (timeCal.get(Calendar.HOUR) == 0) { - timeCal.set(Calendar.HOUR, 12); - } - } - - if (cal == null) { - cal = timeCal; - } else { - cal.set(Calendar.HOUR, timeCal.get(Calendar.HOUR)); - cal.set(Calendar.MINUTE, timeCal.get(Calendar.MINUTE)); - cal.set(Calendar.SECOND, timeCal.get(Calendar.SECOND)); - cal.set(Calendar.AM_PM, timeCal.get(Calendar.AM_PM)); - } - break; - } - } - - if (cal - != null) { // if at least one of the above has been called, write to task. else do nothing. - if (!isNullOrEmpty(inputText)) { - task.setTitle(inputText); - } - if (containsSpecificTime) { - task.setDueDate( - Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, cal.getTime().getTime())); - } else { - task.setDueDate(Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, cal.getTime().getTime())); - } - } - } - // ---------------------DATE-------------------------- - - // Parses through the text and sets the frequency of the task. - private static void repeatHelper(Task task) { - String inputText = task.getTitle(); - HashMap repeatTimes = new HashMap<>(); - repeatTimes.put("(?i)\\bevery ?\\w{0,6} days?\\b", Frequency.DAILY); - repeatTimes.put("(?i)\\bevery ?\\w{0,6} ?nights?\\b", Frequency.DAILY); - repeatTimes.put("(?i)\\bevery ?\\w{0,6} ?mornings?\\b", Frequency.DAILY); - repeatTimes.put("(?i)\\bevery ?\\w{0,6} ?evenings?\\b", Frequency.DAILY); - repeatTimes.put("(?i)\\bevery ?\\w{0,6} ?afternoons?\\b", Frequency.DAILY); - repeatTimes.put("(?i)\\bevery \\w{0,6} ?weeks?\\b", Frequency.WEEKLY); - repeatTimes.put( - "(?i)\\bevery \\w{0,6} ?(mon|tues|wednes|thurs|fri|satur|sun)days?\\b", Frequency.WEEKLY); - repeatTimes.put("(?i)\\bevery \\w{0,6} ?months?\\b", Frequency.MONTHLY); - repeatTimes.put("(?i)\\bevery \\w{0,6} ?years?\\b", Frequency.YEARLY); - - HashMap repeatTimesIntervalOne = new HashMap<>(); - // pre-determined intervals of 1 - repeatTimesIntervalOne.put("(?i)\\bdaily\\b", Frequency.DAILY); - repeatTimesIntervalOne.put("(?i)\\beveryday\\b", Frequency.DAILY); - repeatTimesIntervalOne.put("(?i)\\bweekly\\b", Frequency.WEEKLY); - repeatTimesIntervalOne.put("(?i)\\bmonthly\\b", Frequency.MONTHLY); - repeatTimesIntervalOne.put("(?i)\\byearly\\b", Frequency.YEARLY); - - Set keys = repeatTimes.keySet(); - for (String repeatTime : keys) { - Pattern pattern = Pattern.compile(repeatTime); - Matcher m = pattern.matcher(inputText); - if (m.find()) { - Frequency rtime = repeatTimes.get(repeatTime); - RRule rrule = new RRule(); - rrule.setFreq(rtime); - rrule.setInterval(findInterval(inputText)); - task.setRecurrence(rrule.toIcal()); - return; - } - } - - for (String repeatTimeIntervalOne : repeatTimesIntervalOne.keySet()) { - Pattern pattern = Pattern.compile(repeatTimeIntervalOne); - Matcher m = pattern.matcher(inputText); - if (m.find()) { - Frequency rtime = repeatTimesIntervalOne.get(repeatTimeIntervalOne); - RRule rrule = new RRule(); - rrule.setFreq(rtime); - rrule.setInterval(1); - String thing = rrule.toIcal(); - task.setRecurrence(thing); - return; - } - } - } - - // helper method for repeatHelper. - private static int findInterval(String inputText) { - HashMap wordsToNum = new HashMap<>(); - String[] words = - new String[] { - "one", "two", "three", "four", "five", "six", - "seven", "eight", "nine", "ten", "eleven", "twelve" - }; - for (int i = 0; i < words.length; i++) { - wordsToNum.put(words[i], i + 1); - wordsToNum.put(Integer.toString(i + 1), i + 1); - } - wordsToNum.put("other", 2); - - Pattern pattern = Pattern.compile("(?i)\\bevery (\\w*)\\b"); - int interval = 1; - Matcher m = pattern.matcher(inputText); - if (m.find() && m.group(1) != null) { - String intervalStr = m.group(1); - if (wordsToNum.containsKey(intervalStr)) { - interval = wordsToNum.get(intervalStr); - } else { - try { - interval = Integer.parseInt(intervalStr); - } catch (NumberFormatException e) { - // Ah well - Timber.e(e); - } - } - } - return interval; - } - - // helper method for DayHelper. Resets the time on the calendar to 00:00:00 am - private static void setCalendarToDefaultTime(Calendar cal) { - cal.set(Calendar.HOUR, 0); - cal.set(Calendar.HOUR_OF_DAY, 0); - cal.set(Calendar.MINUTE, 0); - cal.set(Calendar.SECOND, 0); - cal.set(Calendar.AM_PM, Calendar.AM); - } -} diff --git a/app/src/main/java/com/todoroo/astrid/utility/TitleParser.kt b/app/src/main/java/com/todoroo/astrid/utility/TitleParser.kt new file mode 100644 index 000000000..fadde08fa --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/utility/TitleParser.kt @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2012 Todoroo Inc + * + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.utility + +import com.google.ical.values.Frequency +import com.google.ical.values.RRule +import com.mdimension.jchronic.AstridChronic +import com.mdimension.jchronic.Chronic +import com.todoroo.astrid.data.Task +import com.todoroo.astrid.data.Task.Companion.createDueDate +import org.tasks.Strings.isNullOrEmpty +import org.tasks.data.TagDataDaoBlocking +import timber.log.Timber +import java.util.* +import java.util.regex.Matcher +import java.util.regex.Pattern + +object TitleParser { + fun parse(tagDataDao: TagDataDaoBlocking, task: Task, tags: ArrayList) { + repeatHelper(task) + listHelper( + tagDataDao, + task, + tags) // Don't need to know if tags affected things since we don't show alerts for them + dayHelper(task) + priorityHelper(task) + } + + fun trimParenthesis(pattern: String): String { + var pattern = pattern + if (pattern[0] == '#' || pattern[0] == '@') { + pattern = pattern.substring(1) + } + return if ('(' == pattern[0]) { + pattern.substring(1, pattern.length - 1) + } else pattern + } + + fun listHelper(tagDataDao: TagDataDaoBlocking, task: Task, tags: ArrayList) { + var inputText = task.title + val tagPattern = Pattern.compile("(\\s|^)#(\\(.*\\)|[^\\s]+)") + val contextPattern = Pattern.compile("(\\s|^)@(\\(.*\\)|[^\\s]+)") + val addedTags: MutableSet = HashSet() + while (true) { + var m = tagPattern.matcher(inputText) + if (m.find()) { + val tag = trimParenthesis(m.group(2)) + tagDataDao + .getTagWithCase(tag) + ?.let { + if (!addedTags.contains(it)) { + tags.add(it) + } + addedTags.add(it) + } + } else { + m = contextPattern.matcher(inputText) + if (m.find()) { + val tag = trimParenthesis(m.group(2)) + tagDataDao + .getTagWithCase(tag) + ?.let { + if (!addedTags.contains(it)) { + tags.add(it) + } + addedTags.add(it) + } + } else { + break + } + } + inputText = inputText!!.substring(0, m.start()) + inputText.substring(m.end()) + } + task.title = inputText!!.trim { it <= ' ' } + } + + private fun strToPriority(priorityStr: String?): Int { + priorityStr?.toLowerCase()?.trim { it <= ' ' } + var priority = Task.Priority.HIGH + if ("0" == priorityStr || "!0" == priorityStr || "least" == priorityStr || "lowest" == priorityStr) { + priority = Task.Priority.NONE + } + if ("!" == priorityStr || "!1" == priorityStr || "bang" == priorityStr || "1" == priorityStr || "low" == priorityStr) { + priority = Task.Priority.LOW + } + if ("!!" == priorityStr || "!2" == priorityStr || "bang bang" == priorityStr || "2" == priorityStr || "high" == priorityStr) { + priority = Task.Priority.MEDIUM + } + return priority + } + + // priorityHelper parses the string and sets the Task's importance + private fun priorityHelper(task: Task) { + var inputText = task.title + val importanceStrings = arrayOf( + "()((^|[^\\w!])!+|(^|[^\\w!])!\\d)($|[^\\w!])", + "()(?i)((\\s?bang){1,})$", + "(?i)(\\spriority\\s?(\\d)$)", + "(?i)(\\sbang\\s?(\\d)$)", + "(?i)()(\\shigh(est)?|\\slow(est)?|\\stop|\\sleast) ?priority$" + ) + for (importanceString in importanceStrings) { + val importancePattern = Pattern.compile(importanceString) + while (true) { + val m = importancePattern.matcher(inputText) + if (m.find()) { + task.priority = strToPriority(m.group(2).trim { it <= ' ' }) + val start = if (m.start() == 0) 0 else m.start() + 1 + inputText = inputText!!.substring(0, start) + inputText.substring(m.end()) + } else { + break + } + } + } + task.title = inputText!!.trim { it <= ' ' } + } + + // helper for dayHelper. Converts am/pm to an int 0/1. + private fun ampmToNumber(amPmString: String?): Int { + var time = Calendar.PM + if (amPmString == null) { + return time + } + val text = amPmString.toLowerCase().trim { it <= ' ' } + if (text == "am" || text == "a.m" || text == "a") { + time = Calendar.AM + } + if (text == "pm" || text == "p.m" || text == "p") { + time = Calendar.PM + } + return time + } + + private fun removeIfParenthetical(m: Matcher, inputText: String?): String? { + val s = m.group() + return if (s.startsWith("(") && s.endsWith(")")) { + inputText!!.substring(0, m.start()) + inputText.substring(m.end()) + } else inputText + } + + private fun stripParens(s: String): String { + var s = s + if (s.startsWith("(")) { + s = s.substring(1) + } + if (s.endsWith(")")) { + s = s.substring(0, s.length - 1) + } + return s + } + + // ---------------------DATE-------------------------- + // Handles setting the task's date. + // Day of week (e.g. Monday, Tuesday,..) is overridden by a set date (e.g. October 23 2013). + // Vague times (e.g. breakfast, night) are overridden by a set time (9 am, at 10, 17:00) + private fun dayHelper(task: Task) { + var inputText = task.title + var cal: Calendar? = null + var containsSpecificTime = false + val daysOfWeek = arrayOf( + "(?i)(\\(|\\b)today(\\)|\\b)", + "(?i)(\\(|\\b)tomorrow(\\)|\\b)", + "(?i)(\\(|\\b)mon(day(\\)|\\b)|(\\)|\\.))", + "(?i)(\\(|\\b)tue(sday(\\)|\\b)|(\\)|\\.))", + "(?i)(\\(|\\b)wed(nesday(\\)|\\b)|(\\)|\\.))", + "(?i)(\\(|\\b)thu(rsday(\\)|\\b)|(\\)|\\.))", + "(?i)(\\(|\\b)fri(day(\\)|\\b)|(\\)|\\.))", + "(?i)(\\(|\\b)sat(urday(\\)|\\b)|(\\)|\\.))", + "(?i)(\\(|\\b)sun(day(\\)|\\b)|(\\)|\\.))" + ) + for (date in daysOfWeek) { + val pattern = Pattern.compile(date) + val m = pattern.matcher(inputText) + if (m.find()) { + val toParse = stripParens(m.group(0)) + cal = AstridChronic.parse(toParse).beginCalendar + inputText = removeIfParenthetical(m, inputText) + // then put it into task + } + } + val dates = arrayOf( + "(?i)(\\(|\\b)(jan(\\.|uary))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", + "(?i)(\\(|\\b)(feb(\\.|ruary))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", + "(?i)(\\(|\\b)(mar(\\.|ch))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", + "(?i)(\\(|\\b)(apr(\\.|il))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", + "(?i)(\\(|\\b)(may())(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", + "(?i)(\\(|\\b)(jun(\\.|e))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", + "(?i)(\\(|\\b)(jul(\\.|y))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", + "(?i)(\\(|\\b)(aug(\\.|ust))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", + "(?i)(\\(|\\b)(sep(\\.|tember))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", + "(?i)(\\(|\\b)(oct(\\.|ober))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", + "(?i)(\\(|\\b)(nov(\\.|ember))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)", + "(?i)(\\(|\\b)(dec(\\.|ember))(\\s(3[0-1]|[0-2]?[0-9])),?( (\\d{4}|\\d{2}))?(\\)|\\b)" + ) + + // m.group(2) = "month" + // m.group(5) = "day" + for (date in dates) { + val pattern = Pattern.compile(date) + val m = pattern.matcher(inputText) + if (m.find()) { + val dateCal = Chronic.parse(m.group(2)).beginCalendar + if (m.group(5) != null) { + dateCal[Calendar.DAY_OF_MONTH] = m.group(5).toInt() + } + val today = Calendar.getInstance() + if (m.group(6) != null) { + dateCal[Calendar.YEAR] = m.group(6).trim { it <= ' ' }.toInt() + } else if (today[Calendar.MONTH] - dateCal[Calendar.MONTH] + > 1) { // if more than a month in the past + dateCal[Calendar.YEAR] = dateCal[Calendar.YEAR] + 1 + } + if (cal == null) { + cal = dateCal + } else { + cal[Calendar.DAY_OF_MONTH] = dateCal[Calendar.DAY_OF_MONTH] + cal[Calendar.MONTH] = dateCal[Calendar.MONTH] + cal[Calendar.YEAR] = dateCal[Calendar.YEAR] + } + inputText = removeIfParenthetical(m, inputText) + } + } + + // for dates in the format MM/DD + val p = Pattern.compile( + "(?i)(\\(|\\b)(1[0-2]|0?[1-9])(\\/|-)(3[0-1]|[0-2]?[0-9])(\\/|-)?(\\d{4}|\\d{2})?(\\)|\\b)") + val match = p.matcher(inputText) + if (match.find()) { + val dCal = Calendar.getInstance() + setCalendarToDefaultTime(dCal) + dCal[Calendar.MONTH] = match.group(2).trim { it <= ' ' }.toInt() - 1 + dCal[Calendar.DAY_OF_MONTH] = match.group(4).toInt() + if (match.group(6) != null && match.group(6).trim { it <= ' ' } != "") { + var yearString = match.group(6) + if (match.group(6).length == 2) { + yearString = "20" + match.group(6) + } + dCal[Calendar.YEAR] = yearString.toInt() + } + if (cal == null) { + cal = dCal + } else { + cal[Calendar.DAY_OF_MONTH] = dCal[Calendar.DAY_OF_MONTH] + cal[Calendar.MONTH] = dCal[Calendar.MONTH] + cal[Calendar.YEAR] = dCal[Calendar.YEAR] + } + inputText = removeIfParenthetical(match, inputText) + } + val dayTimes = HashMap() + dayTimes["(?i)\\bbreakfast\\b"] = 8 + dayTimes["(?i)\\blunch\\b"] = 12 + dayTimes["(?i)\\bsupper\\b"] = 18 + dayTimes["(?i)\\bdinner\\b"] = 18 + dayTimes["(?i)\\bbrunch\\b"] = 10 + dayTimes["(?i)\\bmorning\\b"] = 8 + dayTimes["(?i)\\bafternoon\\b"] = 15 + dayTimes["(?i)\\bevening\\b"] = 19 + dayTimes["(?i)\\bnight\\b"] = 19 + dayTimes["(?i)\\bmidnight\\b"] = 0 + dayTimes["(?i)\\bnoon\\b"] = 12 + val keys: Set = dayTimes.keys + for (dayTime in keys) { + val pattern = Pattern.compile(dayTime) + val m = pattern.matcher(inputText) + if (m.find()) { + containsSpecificTime = true + val timeHour = dayTimes[dayTime]!! + val dayTimesCal = Calendar.getInstance() + setCalendarToDefaultTime(dayTimesCal) + dayTimesCal[Calendar.HOUR] = timeHour + if (cal == null) { + cal = dayTimesCal + } else { + setCalendarToDefaultTime(cal) + cal[Calendar.HOUR] = timeHour + } + } + } + val times = arrayOf( // [time] am/pm + "(?i)(\\b)([01]?\\d):?([0-5]\\d)? ?([ap]\\.?m?\\.?)\\b", // army time + "(?i)\\b(([0-2]?[0-9]):([0-5][0-9]))(\\b)", // [int] o'clock + "(?i)\\b(([01]?\\d)() ?o'? ?clock) ?([ap]\\.?m\\.?)?\\b", // at [int] + "(?i)(\\bat) ([01]?\\d)()($|\\D($|\\D))" // m.group(2) holds the hour + // m.group(3) holds the minutes + // m.group(4) holds am/pm + ) + for (time in times) { + val pattern = Pattern.compile(time) + val m = pattern.matcher(inputText) + if (m.find()) { + containsSpecificTime = true + val today = Calendar.getInstance() + val timeCal = Calendar.getInstance() + setCalendarToDefaultTime(timeCal) + timeCal[Calendar.HOUR] = m.group(2).toInt() + if (m.group(3) != null && m.group(3).trim { it <= ' ' } != "") { + timeCal[Calendar.MINUTE] = m.group(3).toInt() + } else { + timeCal[Calendar.MINUTE] = 0 + } + if (m.group(2).toInt() <= 12) { + timeCal[Calendar.AM_PM] = ampmToNumber(m.group(4)) + } + + // sets it to the next occurrence of that hour if no am/pm is provided. doesn't include + // military time + if (m.group(2).toInt() <= 12 + && (m.group(4) == null || m.group(4).trim { it <= ' ' } == "")) { + while (timeCal.time.time < today.time.time) { + timeCal[Calendar.HOUR_OF_DAY] = timeCal[Calendar.HOUR_OF_DAY] + 12 + } + } else { // if am/pm is provided and the time is in the past, set it to the next day. + // Military time included. + if (timeCal[Calendar.HOUR] != 0 + && timeCal.time.time < today.time.time) { + timeCal[Calendar.DAY_OF_MONTH] = timeCal[Calendar.DAY_OF_MONTH] + 1 + } + if (timeCal[Calendar.HOUR] == 0) { + timeCal[Calendar.HOUR] = 12 + } + } + if (cal == null) { + cal = timeCal + } else { + cal[Calendar.HOUR] = timeCal[Calendar.HOUR] + cal[Calendar.MINUTE] = timeCal[Calendar.MINUTE] + cal[Calendar.SECOND] = timeCal[Calendar.SECOND] + cal[Calendar.AM_PM] = timeCal[Calendar.AM_PM] + } + break + } + } + if (cal + != null) { // if at least one of the above has been called, write to task. else do nothing. + if (!isNullOrEmpty(inputText)) { + task.title = inputText + } + if (containsSpecificTime) { + task.dueDate = createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, cal.time.time) + } else { + task.dueDate = createDueDate(Task.URGENCY_SPECIFIC_DAY, cal.time.time) + } + } + } + + // ---------------------DATE-------------------------- + // Parses through the text and sets the frequency of the task. + private fun repeatHelper(task: Task) { + val inputText = task.title + val repeatTimes = HashMap() + repeatTimes["(?i)\\bevery ?\\w{0,6} days?\\b"] = Frequency.DAILY + repeatTimes["(?i)\\bevery ?\\w{0,6} ?nights?\\b"] = Frequency.DAILY + repeatTimes["(?i)\\bevery ?\\w{0,6} ?mornings?\\b"] = Frequency.DAILY + repeatTimes["(?i)\\bevery ?\\w{0,6} ?evenings?\\b"] = Frequency.DAILY + repeatTimes["(?i)\\bevery ?\\w{0,6} ?afternoons?\\b"] = Frequency.DAILY + repeatTimes["(?i)\\bevery \\w{0,6} ?weeks?\\b"] = Frequency.WEEKLY + repeatTimes["(?i)\\bevery \\w{0,6} ?(mon|tues|wednes|thurs|fri|satur|sun)days?\\b"] = Frequency.WEEKLY + repeatTimes["(?i)\\bevery \\w{0,6} ?months?\\b"] = Frequency.MONTHLY + repeatTimes["(?i)\\bevery \\w{0,6} ?years?\\b"] = Frequency.YEARLY + val repeatTimesIntervalOne = HashMap() + // pre-determined intervals of 1 + repeatTimesIntervalOne["(?i)\\bdaily\\b"] = Frequency.DAILY + repeatTimesIntervalOne["(?i)\\beveryday\\b"] = Frequency.DAILY + repeatTimesIntervalOne["(?i)\\bweekly\\b"] = Frequency.WEEKLY + repeatTimesIntervalOne["(?i)\\bmonthly\\b"] = Frequency.MONTHLY + repeatTimesIntervalOne["(?i)\\byearly\\b"] = Frequency.YEARLY + val keys: Set = repeatTimes.keys + for (repeatTime in keys) { + val pattern = Pattern.compile(repeatTime) + val m = pattern.matcher(inputText) + if (m.find()) { + val rtime = repeatTimes[repeatTime] + val rrule = RRule() + rrule.freq = rtime + rrule.interval = findInterval(inputText) + task.recurrence = rrule.toIcal() + return + } + } + for (repeatTimeIntervalOne in repeatTimesIntervalOne.keys) { + val pattern = Pattern.compile(repeatTimeIntervalOne) + val m = pattern.matcher(inputText) + if (m.find()) { + val rtime = repeatTimesIntervalOne[repeatTimeIntervalOne] + val rrule = RRule() + rrule.freq = rtime + rrule.interval = 1 + val thing = rrule.toIcal() + task.recurrence = thing + return + } + } + } + + // helper method for repeatHelper. + private fun findInterval(inputText: String?): Int { + val wordsToNum = HashMap() + val words = arrayOf( + "one", "two", "three", "four", "five", "six", + "seven", "eight", "nine", "ten", "eleven", "twelve" + ) + for (i in words.indices) { + wordsToNum[words[i]] = i + 1 + wordsToNum[Integer.toString(i + 1)] = i + 1 + } + wordsToNum["other"] = 2 + val pattern = Pattern.compile("(?i)\\bevery (\\w*)\\b") + var interval = 1 + val m = pattern.matcher(inputText) + if (m.find() && m.group(1) != null) { + val intervalStr = m.group(1) + if (wordsToNum.containsKey(intervalStr)) { + interval = wordsToNum[intervalStr]!! + } else { + try { + interval = intervalStr.toInt() + } catch (e: NumberFormatException) { + // Ah well + Timber.e(e) + } + } + } + return interval + } + + // helper method for DayHelper. Resets the time on the calendar to 00:00:00 am + private fun setCalendarToDefaultTime(cal: Calendar) { + cal[Calendar.HOUR] = 0 + cal[Calendar.HOUR_OF_DAY] = 0 + cal[Calendar.MINUTE] = 0 + cal[Calendar.SECOND] = 0 + cal[Calendar.AM_PM] = Calendar.AM + } +} \ No newline at end of file