Convert TitleParser to Kotlin

pull/1055/head
Alex Baker 4 years ago
parent b44150c7f3
commit 3db92db33b

@ -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) {

@ -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<String> 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<String> tags) {
String inputText = task.getTitle();
Pattern tagPattern = Pattern.compile("(\\s|^)#(\\(.*\\)|[^\\s]+)");
Pattern contextPattern = Pattern.compile("(\\s|^)@(\\(.*\\)|[^\\s]+)");
Set<String> 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<String, Integer> 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<String> 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<String, Frequency> 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<String, Frequency> 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<String> 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<String, Integer> 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);
}
}

@ -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<String>) {
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<String>) {
var inputText = task.title
val tagPattern = Pattern.compile("(\\s|^)#(\\(.*\\)|[^\\s]+)")
val contextPattern = Pattern.compile("(\\s|^)@(\\(.*\\)|[^\\s]+)")
val addedTags: MutableSet<String?> = 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<String, Int>()
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<String> = 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<String, Frequency>()
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<String, Frequency>()
// 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<String> = 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<String, Int?>()
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
}
}
Loading…
Cancel
Save