[california/wip/725792-quick-add] Additional date parsing and better StackLookahead mark/restore
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [california/wip/725792-quick-add] Additional date parsing and better StackLookahead mark/restore
- Date: Sat, 19 Apr 2014 02:45:27 +0000 (UTC)
commit 2fcb47ce344146c4a87e7927daa5fd4f3cc82109
Author: Jim Nelson <jim yorba org>
Date: Fri Apr 18 19:44:51 2014 -0700
Additional date parsing and better StackLookahead mark/restore
src/calendar/calendar-day-of-week.vala | 1 +
src/calendar/calendar-month.vala | 19 ++++
src/collection/collection-lookahead-stack.vala | 92 +++++++++++--------
src/component/component-details-parser.vala | 118 ++++++++++++++++++++----
src/component/component.vala | 9 ++-
5 files changed, 182 insertions(+), 57 deletions(-)
---
diff --git a/src/calendar/calendar-day-of-week.vala b/src/calendar/calendar-day-of-week.vala
index 80aeb57..dae4e05 100644
--- a/src/calendar/calendar-day-of-week.vala
+++ b/src/calendar/calendar-day-of-week.vala
@@ -186,6 +186,7 @@ public class DayOfWeek : BaseObject, Gee.Hashable<DayOfWeek> {
public static DayOfWeek? parse(string str) {
string token = str.strip().casefold();
+ // a lookup map may make sense here
foreach (DayOfWeek dow in days_of_week_monday) {
if (dow.abbrev_name.casefold() == token)
return dow;
diff --git a/src/calendar/calendar-month.vala b/src/calendar/calendar-month.vala
index 5a62adc..ffdca5a 100644
--- a/src/calendar/calendar-month.vala
+++ b/src/calendar/calendar-month.vala
@@ -131,6 +131,25 @@ public class Month : BaseObject, Gee.Comparable<Month>, Gee.Hashable<Month> {
return for_checked(gdate.get_month());
}
+ /**
+ * Compares the supplied string with all translated { link Month} names, both { link abbrev_name}
+ * and { link full_name}.
+ */
+ public static Month? parse(string str) {
+ string casefolded = str.casefold();
+
+ // a lookup map may make sense here
+ foreach (Month month in months) {
+ if (month.full_name.casefold() == casefolded)
+ return month;
+
+ if (month.abbrev_name.casefold() == casefolded)
+ return month;
+ }
+
+ return null;
+ }
+
internal inline DateMonth to_date_month() {
return (DateMonth) value;
}
diff --git a/src/collection/collection-lookahead-stack.vala b/src/collection/collection-lookahead-stack.vala
index 0bfadad..0b996ce 100644
--- a/src/collection/collection-lookahead-stack.vala
+++ b/src/collection/collection-lookahead-stack.vala
@@ -18,54 +18,50 @@ public class LookaheadStack<G> : BaseObject {
/**
* Returns true if no elements are in the queue.
*/
- public bool is_empty { get { return deque.is_empty; } }
+ public bool is_empty { get { return stack.is_empty; } }
/**
* Returns the number of elements remaining in the stack.
*/
- public int size { get { return deque.size; } }
+ public int size { get { return stack.size; } }
/**
* Returns true if the stack is marked.
*
* @see mark
*/
- public bool is_marked { get { return marked != null; } }
-
- /**
- * Returns the number of elements residing as marked state.
- *
- * @see mark
- */
- public int marked_size { get { return (marked != null) ? marked.size : 0; } }
+ public int markpoint_count { get { return markpoints.size + (markpoint != null ? 1 : 0); } }
/**
* Returns the current element at the top of the stack.
*/
- public G? top { owned get { return deque.peek_head(); } }
+ public G? top { owned get { return stack.peek_head(); } }
- private Gee.Deque<G> deque;
- private Gee.Queue<G>? marked = null;
+ private Gee.Deque<G> stack;
+ private Gee.Deque<Gee.Deque<G>>? markpoints;
+ private Gee.Deque<G>? markpoint = null;
public LookaheadStack(Gee.Collection<G> init) {
// must be initialized here; see
// https://bugzilla.gnome.org/show_bug.cgi?id=523767
- deque = new Gee.LinkedList<G>();
- deque.add_all(init);
+ stack = new Gee.LinkedList<G>();
+ stack.add_all(init);
+
+ markpoints = new Gee.LinkedList<Gee.Deque<G>>();
}
/**
* Returns null if empty.
*/
public G? pop() {
- if (deque.is_empty)
+ if (stack.is_empty)
return null;
- G element = deque.poll_head();
+ G element = stack.poll_head();
- // if stack is marked, save for potential restoration
- if (marked != null)
- marked.offer(element);
+ // if markpoint set, save element for later
+ if (markpoint != null)
+ markpoint.offer_head(element);
return element;
}
@@ -73,37 +69,59 @@ public class LookaheadStack<G> : BaseObject {
/**
* Marks the state of the stack so it can be restored with { link restore}.
*
- * Marking a marked stack is functionally equivalent as calling { link unmark} followed by
- * this method.
+ * Multiple markpoints can be made, each requiring a matching { link restore} to return to the
+ * state.
*/
public void mark() {
- marked = new Gee.LinkedList<G>();
+ if (markpoint != null)
+ markpoints.offer_head(markpoint);
+
+ markpoint = new Gee.LinkedList<G>();
}
/**
- * Restores the state of the stack to the point when { link mark} was called.
- *
- * restore() implies { link unmark}.
+ * Restores the state of the stack to the point when the last markpoint was made.
*
- * This does nothing if mark() was not first called.
+ * This does nothing if { link mark} was not first called.
*/
public void restore() {
- if (marked == null)
- return;
+ if (markpoint != null) {
+ // restore elements as stored in marked queue
+ while (!markpoint.is_empty)
+ stack.offer_head(markpoint.poll_head());
+ }
- // restore elements as stored in marked queue
- while (!marked.is_empty)
- deque.offer_head(marked.poll());
-
- // now unmarked
- marked = null;
+ // pop last marked state, if any, as the current marked state
+ pop_markpoint();
}
/**
- * Unmarks the stack, making restoration of popped elements impossible.
+ * Drops the last markpoint, if any.
+ *
+ * This is functionally equivalent to { link restore}, but the current markpoint elements are
+ * not added back to the stack. Prior markpoints remain.
+ *
+ * @see mark
*/
public void unmark() {
- marked = null;
+ pop_markpoint();
+ }
+
+ /**
+ * Drops all markpoints.
+ *
+ * @see mark
+ */
+ public void clear_marks() {
+ markpoint = null;
+ markpoints.clear();
+ }
+
+ private void pop_markpoint() {
+ if (!markpoints.is_empty)
+ markpoint = markpoints.poll_head();
+ else
+ markpoint = null;
}
public override string to_string() {
diff --git a/src/component/component-details-parser.vala b/src/component/component-details-parser.vala
index 724b3af..7454ce6 100644
--- a/src/component/component-details-parser.vala
+++ b/src/component/component-details-parser.vala
@@ -42,6 +42,11 @@ public class DetailsParser : BaseObject {
}
/**
+ * The original string of text generating the { link event}.
+ */
+ public string details { get; private set; }
+
+ /**
* The generated { link Event}.
*/
public Component.Event event { get; private set; default = new Component.Event.blank(); }
@@ -66,6 +71,8 @@ public class DetailsParser : BaseObject {
* If the details string is empty, a blank Event is generated.
*/
public DetailsParser(string? details) {
+ this.details = details;
+
// tokenize the string and arrange as a stack for the parser
string[] tokenized = String.reduce_whitespace(details ?? "").split(" ");
Gee.LinkedList<Token> list = new Gee.LinkedList<Token>();
@@ -174,6 +181,7 @@ public class DetailsParser : BaseObject {
if (start_date != null && end_date == null)
end_date = midnight_crossed ? start_date.adjust(1, Calendar.DateUnit.DAY) : start_date;
+ debug("details: %s", details);
debug("start time: %s", (start_time != null) ? start_time.to_string() : "(null)");
debug("end time: %s", (end_time != null) ? end_time.to_string() : "(null)");
debug("duration: %s", (duration != null) ? duration.to_string() : "(null)");
@@ -207,23 +215,50 @@ public class DetailsParser : BaseObject {
if (specifier == null)
return false;
- // parse looking for time or date ... time specifier doesn't have to be strictly parsed,
- // i.e. "8" -> 8am is okay, since the time preposition offers a clue
- Calendar.WallTime? wall_time = null;
- Calendar.Date? date = parse_relative_date(specifier.casefolded);
- if (date == null) {
- bool liberally_parsed;
- wall_time = Calendar.WallTime.parse(specifier.casefolded, out liberally_parsed);
- if (wall_time != null && liberally_parsed && strict)
- return false;
+ // go for the gusto and look for 2 or 3 specifiers in total, month and day or month/day/year
+ // in any order
+ stack.mark();
+ {
+ Token? second = stack.pop();
+ if (second != null) {
+ Calendar.Date? date = parse_day_month(specifier, second);
+ if (date == null)
+ date = parse_day_month(second, specifier);
+
+ if (date != null && add_date(date))
+ return true;
+ }
}
+ stack.restore();
- if (wall_time != null)
- return add_wall_time(wall_time);
- else if (date != null)
- return add_date(date);
- else
+ stack.mark();
+ {
+ Token? second = stack.pop();
+ Token? third = stack.pop();
+ if (second != null && third != null) {
+ // try d/m/y followed by m/d/y ... every other combination seems overkill
+ Calendar.Date? date = parse_day_month_year(specifier, second, third);
+ if (date == null)
+ date = parse_day_month_year(second, specifier, third);
+
+ if (date != null && add_date(date))
+ return true;
+ }
+ }
+ stack.restore();
+
+ // parse single specifier looking for date first, then time
+ Calendar.Date? date = parse_relative_date(specifier);
+ if (date != null && add_date(date))
+ return true;
+
+ bool liberally_parsed;
+ Calendar.WallTime? wall_time = Calendar.WallTime.parse(specifier.casefolded,
+ out liberally_parsed);
+ if (wall_time != null && liberally_parsed && strict)
return false;
+
+ return (wall_time != null) ? add_wall_time(wall_time) : false;
}
// Add a duration to the event if not already specified and an end time has not already been
@@ -289,17 +324,17 @@ public class DetailsParser : BaseObject {
}
// Parses a potential date specifier into a calendar date relative to today
- private Calendar.Date? parse_relative_date(string casefolded) {
+ private Calendar.Date? parse_relative_date(Token token) {
// attempt to parse into common words for relative dates
- if (casefolded == TODAY)
+ if (token.casefolded == TODAY)
return Calendar.System.today;
- else if (casefolded == TOMORROW)
+ else if (token.casefolded == TOMORROW)
return Calendar.System.today.next();
- else if (casefolded == YESTERDAY)
+ else if (token.casefolded == YESTERDAY)
return Calendar.System.today.previous();
// attempt to parse into day of the week
- Calendar.DayOfWeek? dow = Calendar.DayOfWeek.parse(casefolded);
+ Calendar.DayOfWeek? dow = Calendar.DayOfWeek.parse(token.casefolded);
if (dow == null)
return null;
@@ -317,6 +352,51 @@ public class DetailsParser : BaseObject {
return null;
}
+ // Parses potential date specifiers into a specific calendar date
+ private Calendar.Date? parse_day_month(Token day, Token mon, Calendar.Year? year = null) {
+ // strip ordinal suffix if present
+ string day_number = day.casefolded;
+ foreach (string suffix in ORDINAL_SUFFIXES) {
+ if (!String.is_empty(suffix) && day_number.has_suffix(suffix)) {
+ day_number = day_number.slice(0, day_number.length - suffix.length);
+
+ break;
+ }
+ }
+
+ if (!String.is_numeric(day_number))
+ return null;
+
+ Calendar.Month? month = Calendar.Month.parse(mon.casefolded);
+ if (month == null)
+ return null;
+
+ if (year == null)
+ year = Calendar.System.today.year;
+
+ try {
+ return new Calendar.Date(Calendar.DayOfMonth.for(int.parse(day.casefolded)),
+ month, year);
+ } catch (CalendarError calerr) {
+ // probably an out-of-bounds day of month
+ return null;
+ }
+ }
+
+ // Parses potential date specifiers into a specific calendar date
+ private Calendar.Date? parse_day_month_year(Token day, Token mon, Token yr) {
+ if (!String.is_numeric(yr.casefolded))
+ return null;
+
+ // a *sane* year
+ int year = int.parse(yr.casefolded);
+ int current_year = Calendar.System.today.year.value;
+ if (year < (current_year - 1) || (year > current_year + 6))
+ return null;
+
+ return parse_day_month(day, mon, new Calendar.Year(year));
+ }
+
// Adds a date to the event, start time first, then end time, dropping dates thereafter
private bool add_date(Calendar.Date date) {
if (start_date == null)
diff --git a/src/component/component.vala b/src/component/component.vala
index a4e80a2..e929eda 100644
--- a/src/component/component.vala
+++ b/src/component/component.vala
@@ -24,6 +24,7 @@ private string[] TIME_PREPOSITIONS;
private string[] LOCATION_PREPOSITIONS;
private string[] DURATION_PREPOSITIONS;
private string[] DELAY_PREPOSITIONS;
+private string[] ORDINAL_SUFFIXES;
public void init() throws Error {
if (!Unit.do_init(ref init_count))
@@ -76,13 +77,19 @@ public void init() throws Error {
// parser.
// Example: "at supermarket", "at Eiffel Tower"
LOCATION_PREPOSITIONS = _("at;").split(";");
+
+ // Used by quick-add to strip date numbers of common ordinal suffices. All suffixes should
+ // be casefolded (lowercase) and delimited by semi-colons. The list can be empty, but that
+ // will limit the parser if your language supports ordinal suffixes.
+ // Example: "1st", "2nd", "3rd", "4th"
+ ORDINAL_SUFFIXES = _("st;nd;rd;th").split(";");
}
public void terminate() {
if (!Unit.do_terminate(ref init_count))
return;
- TIME_PREPOSITIONS = LOCATION_PREPOSITIONS = DURATION_PREPOSITIONS = null;
+ TIME_PREPOSITIONS = LOCATION_PREPOSITIONS = DURATION_PREPOSITIONS = ORDINAL_SUFFIXES = null;
Calendar.terminate();
Collection.terminate();
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]