[california/wip/725785-create-recurring] More weekly tests, fixed encoding/decode days (again)



commit 6bbea57ac297c142a31cc5b1e773eb618d2d79a0
Author: Jim Nelson <jim yorba org>
Date:   Fri Jun 20 15:15:31 2014 -0700

    More weekly tests, fixed encoding/decode days (again)

 src/calendar/calendar-day-of-week.vala       |   27 ++++--
 src/collection/collection-iterable.vala      |   11 +++
 src/component/component-details-parser.vala  |   67 +++++++++++++-
 src/component/component-recurrence-rule.vala |   33 +++++---
 src/component/component.vala                 |   23 +++++-
 src/tests/tests-quick-add-recurring.vala     |  125 ++++++++++++++++++++++++++
 6 files changed, 259 insertions(+), 27 deletions(-)
---
diff --git a/src/calendar/calendar-day-of-week.vala b/src/calendar/calendar-day-of-week.vala
index ee29481..4fc8500 100644
--- a/src/calendar/calendar-day-of-week.vala
+++ b/src/calendar/calendar-day-of-week.vala
@@ -173,16 +173,7 @@ public class DayOfWeek : BaseObject, Gee.Hashable<DayOfWeek> {
         if (index < 0 || index >= COUNT)
             throw new CalendarError.INVALID("Invalid day of week value %d", value);
         
-        switch (first_of_week) {
-            case FirstOfWeek.MONDAY:
-                return days_of_week_monday[index];
-            
-            case FirstOfWeek.SUNDAY:
-                return days_of_week_sunday[index];
-            
-            default:
-                assert_not_reached();
-        }
+        return all(first_of_week)[index];
     }
     
     /**
@@ -197,6 +188,22 @@ public class DayOfWeek : BaseObject, Gee.Hashable<DayOfWeek> {
         }
     }
     
+    /**
+     * Return all { link DayOfWeeks} ordered by the { link FirstOfWeek}.
+     */
+    public static unowned DayOfWeek[] all(FirstOfWeek first_of_week) {
+        switch (first_of_week) {
+            case FirstOfWeek.MONDAY:
+                return days_of_week_monday;
+            
+            case FirstOfWeek.SUNDAY:
+                return days_of_week_sunday;
+            
+            default:
+                assert_not_reached();
+        }
+    }
+    
     internal static DayOfWeek from_gdate(GLib.Date date) {
         assert(date.valid());
         
diff --git a/src/collection/collection-iterable.vala b/src/collection/collection-iterable.vala
index 55863d8..b551cd2 100644
--- a/src/collection/collection-iterable.vala
+++ b/src/collection/collection-iterable.vala
@@ -192,6 +192,17 @@ public class Iterable<G> : Object {
         return new GeeIterable<G>(i);
     }
     
+    /**
+     * Convert the { link Iterable} into a flat array of elements.
+     */
+    public G[] to_array() {
+        G[] ar = new G[0];
+        while (i.next())
+            ar += i.get();
+        
+        return ar;
+    }
+    
     public Gee.Collection<G> add_all_to(Gee.Collection<G> c) {
         while (i.next())
             c.add(i  get());
diff --git a/src/component/component-details-parser.vala b/src/component/component-details-parser.vala
index 4b892c3..ecebe74 100644
--- a/src/component/component-details-parser.vala
+++ b/src/component/component-details-parser.vala
@@ -179,6 +179,18 @@ public class DetailsParser : BaseObject {
                 continue;
             }
             
+            // if a recurring rule has been started and are adding to it, drop common prepositions
+            // that indicate linkage
+            if (rrule != null && token.casefolded in COMMON_PREPOSITIONS)
+                continue;
+            
+            // if a recurring rule has not been started, look for keywords which transform the
+            // event into one
+            stack.mark();
+            if (rrule == null && parse_recurring_indicator(token))
+                continue;
+            stack.restore();
+            
             // if a recurring rule has been started, attempt to parse into additions for the rule
             stack.mark();
             if (rrule != null && parse_recurring(token))
@@ -477,6 +489,43 @@ public class DetailsParser : BaseObject {
         return null;
     }
     
+    // this can create a new RRULE if the token indicates a one-time event should be recurring
+    private bool parse_recurring_indicator(Token? specifier) {
+        // rrule can't already exist
+        if (rrule != null || specifier == null)
+            return false;
+        
+        if (specifier.casefolded == DAILY) {
+            set_rrule_daily(1);
+            
+            return true;
+        }
+        
+        if (specifier.casefolded == WEEKLY) {
+            // set the start date to today unless already known
+            if (start_date == null)
+                start_date = Calendar.System.today;
+            
+            set_rrule_weekly(iterate<Calendar.DayOfWeek>(start_date.day_of_week).to_array(), 1);
+            
+            return true;
+        }
+        
+        if (specifier.casefolded in UNIT_WEEKDAYS) {
+            set_rrule_weekly(Calendar.DayOfWeek.weekdays, 1);
+            
+            return true;
+        }
+        
+        if (specifier.casefolded in UNIT_WEEKENDS) {
+            set_rrule_weekly(Calendar.DayOfWeek.weekend_days, 1);
+            
+            return true;
+        }
+        
+        return false;
+    }
+    
     // this can create a new RRULE or edit an existing one, but will not create multiple RRULEs
     // for the same VEVENT
     private bool parse_recurring(Token? specifier) {
@@ -509,10 +558,7 @@ public class DetailsParser : BaseObject {
         
         // "day"
         if (unit.casefolded in UNIT_DAYS) {
-            start_date = Calendar.System.today;
-            rrule = new RecurrenceRule(iCal.icalrecurrencetype_frequency.DAILY_RECURRENCE);
-            rrule.interval = interval;
-            rrule.first_of_week = Calendar.System.first_of_week.as_day_of_week();
+            set_rrule_daily(interval);
             
             return true;
         }
@@ -554,6 +600,19 @@ public class DetailsParser : BaseObject {
         return false;
     }
     
+    private void set_rrule_daily(int interval) {
+        if (rrule != null)
+            return;
+        
+        // no start_date at this point, then today is it
+        if (start_date == null)
+            start_date = Calendar.System.today;
+        
+        rrule = new RecurrenceRule(iCal.icalrecurrencetype_frequency.DAILY_RECURRENCE);
+        rrule.interval = interval;
+        rrule.first_of_week = Calendar.System.first_of_week.as_day_of_week();
+    }
+    
     private void set_rrule_weekly(Calendar.DayOfWeek[]? by_days, int interval) {
         Gee.Map<Calendar.DayOfWeek?, int> map = new Gee.HashMap<Calendar.DayOfWeek?, int>();
         if (by_days != null) {
diff --git a/src/component/component-recurrence-rule.vala b/src/component/component-recurrence-rule.vala
index 59b6f6a..289a4b3 100644
--- a/src/component/component-recurrence-rule.vala
+++ b/src/component/component-recurrence-rule.vala
@@ -285,9 +285,13 @@ public class RecurrenceRule : BaseObject {
      * @see decode_day
      */
     public static int encode_day(Calendar.DayOfWeek? dow, int position) {
-        int dow_value = (dow != null) ? dow.ordinal(Calendar.System.first_of_week) : 0;
+        // these encodings are mapped to iCal.icalrecurrencetype_weekday, which is SUNDAY-based
+        int dow_value = (dow != null) ? dow.ordinal(Calendar.FirstOfWeek.SUNDAY) : 0;
         
-        return (position.clamp(0, int.MAX) * 7) + dow_value;
+        position = position.clamp(short.MIN, short.MAX);
+        int value = (position * 8) + (position >= 0 ? dow_value : 0 - dow_value);
+        
+        return value;
     }
     
     /**
@@ -302,17 +306,16 @@ public class RecurrenceRule : BaseObject {
      * Returns false if the supplied value is definitely not encoded correctly.
      */
     public static bool decode_day(int value, out Calendar.DayOfWeek? dow, out int position) {
-        position = value / 7;
+        position = iCal.icalrecurrencetype.day_position((short) value);
         
         dow = null;
-        int dow_value = value % 7;
+        int dow_value = (int) iCal.icalrecurrencetype.day_day_of_week((short) value);
         if (dow_value != 0) {
             try {
-                dow = Calendar.DayOfWeek.for(dow_value, Calendar.System.first_of_week);
-            } catch (CalendarError cal_err) {
-                debug("Unable to decode %d: %d is invalid day of week", value, dow_value);
-                
-                return false;
+                // iCal.icalrecurrencetype_weekday is SUNDAY-based
+                dow = Calendar.DayOfWeek.for(dow_value, Calendar.FirstOfWeek.SUNDAY);
+            } catch (CalendarError calerr) {
+                debug("Unable to decode day of week value %d: %s", dow_value, calerr.message);
             }
         }
         
@@ -408,6 +411,10 @@ public class RecurrenceRule : BaseObject {
         return get_by_set(by_rule).read_only_view;
     }
     
+    private bool is_int_short(int value) {
+        return value >= short.MIN && value <= short.MAX;
+    }
+    
     /**
      * Replaces the existing set of values for the BY rules with the supplied values.
      *
@@ -416,6 +423,8 @@ public class RecurrenceRule : BaseObject {
      *
      * Pass null or an empty Collection to clear the by-rules values.
      *
+     * Any value greater than short.MAX or less than short.MIN will be dropped.
+     *
      * Use { link encode_days} when passing values for { link ByRule.DAY}.
      *
      * @see add_by_rule
@@ -426,7 +435,7 @@ public class RecurrenceRule : BaseObject {
         
         by_set.clear();
         if (values != null && values.size > 0)
-            by_set.add_all(values);
+            by_set.add_all(traverse<int>(values).filter(is_int_short).to_array_list());
         
         by_rule_updated(by_rule);
     }
@@ -439,6 +448,8 @@ public class RecurrenceRule : BaseObject {
      *
      * Null or an empty Collection is a no-op.
      *
+     * Any value greater than short.MAX or less than short.MIN will be dropped.
+     *
      * Use { link encode_days} when passing values for { link ByRule.DAY}.
      *
      * @see set_by_rule
@@ -448,7 +459,7 @@ public class RecurrenceRule : BaseObject {
         Gee.SortedSet<int> by_set = get_by_set(by_rule);
         
         if (values != null && values.size > 0)
-            by_set.add_all(values);
+            by_set.add_all(traverse<int>(values).filter(is_int_short).to_array_list());
         
         by_rule_updated(by_rule);
     }
diff --git a/src/component/component.vala b/src/component/component.vala
index 7bac566..23f49b2 100644
--- a/src/component/component.vala
+++ b/src/component/component.vala
@@ -20,6 +20,8 @@ private int init_count = 0;
 private string TODAY;
 private string TOMORROW;
 private string YESTERDAY;
+private string DAILY;
+private string WEEKLY;
 private string[] UNIT_WEEKDAYS;
 private string[] UNIT_WEEKENDS;
 private string[] UNIT_YEARS;
@@ -28,6 +30,7 @@ private string[] UNIT_WEEKS;
 private string[] UNIT_DAYS;
 private string[] UNIT_HOURS;
 private string[] UNIT_MINS;
+private string[] COMMON_PREPOSITIONS;
 private string[] TIME_PREPOSITIONS;
 private string[] LOCATION_PREPOSITIONS;
 private string[] DURATION_PREPOSITIONS;
@@ -55,6 +58,14 @@ public void init() throws Error {
     // For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
     YESTERDAY = _("yesterday").casefold();
     
+    // Used by quick-add to indicate the user wants to create a daily recurring event
+    // For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
+    DAILY = _("daily").casefold();
+    
+    // Used by quick-add to indicate the user wants to create a weekly recurring event
+    // For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
+    WEEKLY = _("weekly").casefold();
+    
     // Used by quick-add to indicate the user wants to create an event for every weekday
     // (in most Western countries, this means Monday through Friday, i.e. the work week)
     // Common abbreviations (without punctuation) should be included.  Each word must be separated
@@ -99,6 +110,14 @@ public void init() throws Error {
     // For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
     UNIT_MINS = _("minute;minutes;min;mins").casefold().split(";");
     
+    // Used by quick-add to determine if the word is a COMMON preposition (indicating linkage or a
+    // connection).  Each word must be separate by semi-colons.
+    // These words should not be duplicated in another other preposition list.
+    // This list can be empty but that will limit the parser or cause unexpected results.
+    // Examples: "wednesday and thursday", "monday or friday"
+    // For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
+    COMMON_PREPOSITIONS = _("and;or;").casefold().split(";");
+    
     // Used by quick-add to determine if the word is a TIME preposition (indicating a
     // specific time of day, not a duration).  Each word must be separated by semi-colons.
     // It's allowable for some or all of these words to
@@ -158,8 +177,8 @@ public void terminate() {
         return;
     
     TIME_PREPOSITIONS = LOCATION_PREPOSITIONS = DURATION_PREPOSITIONS = ORDINAL_SUFFIXES = null;
-    DELAY_PREPOSITIONS = RECURRING_PREPOSITIONS = null;
-    TODAY = TOMORROW = YESTERDAY = null;
+    COMMON_PREPOSITIONS = DELAY_PREPOSITIONS = RECURRING_PREPOSITIONS = null;
+    TODAY = TOMORROW = YESTERDAY = DAILY = WEEKLY = null;
     UNIT_WEEKDAYS = UNIT_WEEKENDS = UNIT_YEARS = UNIT_MONTHS = UNIT_WEEKS = UNIT_DAYS = UNIT_HOURS
         = UNIT_MINS = null;
     
diff --git a/src/tests/tests-quick-add-recurring.vala b/src/tests/tests-quick-add-recurring.vala
index 4273a03..8478dca 100644
--- a/src/tests/tests-quick-add-recurring.vala
+++ b/src/tests/tests-quick-add-recurring.vala
@@ -13,9 +13,12 @@ private class QuickAddRecurring : UnitTest.Harness {
         add_case("encode-decode-day-every-month", encode_decode_day_every_month);
         add_case("encode-decode-days-every-week", encode_decode_days_every_week);
         add_case("encode-decode-days-every-month", encode_decode_days_every_month);
+        add_case("encode-decode-all", encode_decode_all);
+        add_case("encode-decode-all_negative", encode_decode_all_negative);
         
         // DAILY tests
         add_case("every-day", every_day);
+        add_case("daily", daily);
         add_case("every-day-10-days", every_day_10_days);
         add_case("every-2-days", every_2_days);
         add_case("every-3rd-day", every_3rd_day);
@@ -24,8 +27,13 @@ private class QuickAddRecurring : UnitTest.Harness {
         
         // WEEKLY
         add_case("every-tuesday", every_tuesday);
+        add_case("tuesday_weekly", tuesday_weekly);
+        add_case("weekdays_to_1pm", weekdays_to_1pm);
+        add_case("weekends", weekends);
+        add_case("every_weekend", every_weekend);
         add_case("every-tuesday-thursday", every_tuesday_thursday);
         add_case("every-tuesday-and-thursday", every_tuesday_and_thursday);
+        add_case("every-tuesday-and-thursday-for-3-weeks", every_tuesday_and_thursday_for_3_weeks);
     }
     
     protected override void setup() throws Error {
@@ -93,6 +101,46 @@ private class QuickAddRecurring : UnitTest.Harness {
             && dows[Calendar.DayOfWeek.WED] == 2;
     }
     
+    private bool encode_decode_all() throws Error {
+        Gee.Collection<Calendar.DayOfWeek> all =
+            
from_array<Calendar.DayOfWeek?>(Calendar.DayOfWeek.all(Calendar.FirstOfWeek.SUNDAY)).to_array_list();
+        
+        int iter = 0;
+        Gee.Collection<int> values = Component.RecurrenceRule.encode_days(
+            traverse<Calendar.DayOfWeek>(all).to_hash_map_as_keys<int>(dow => iter++));
+        Gee.Map<Calendar.DayOfWeek?, int> dows = Component.RecurrenceRule.decode_days(values);
+        
+        return dows.size == 7
+            && dows.has_key(Calendar.DayOfWeek.SUN)
+            && dows[Calendar.DayOfWeek.SUN] == 0
+            && dows[Calendar.DayOfWeek.MON] == 1
+            && dows[Calendar.DayOfWeek.TUE] == 2
+            && dows[Calendar.DayOfWeek.WED] == 3
+            && dows[Calendar.DayOfWeek.THU] == 4
+            && dows[Calendar.DayOfWeek.FRI] == 5
+            && dows[Calendar.DayOfWeek.SAT] == 6;
+    }
+    
+    private bool encode_decode_all_negative() throws Error {
+        Gee.Collection<Calendar.DayOfWeek> all =
+            
from_array<Calendar.DayOfWeek?>(Calendar.DayOfWeek.all(Calendar.FirstOfWeek.SUNDAY)).to_array_list();
+        
+        int iter = -1;
+        Gee.Collection<int> values = Component.RecurrenceRule.encode_days(
+            traverse<Calendar.DayOfWeek>(all).to_hash_map_as_keys<int>(dow => iter--));
+        Gee.Map<Calendar.DayOfWeek?, int> dows = Component.RecurrenceRule.decode_days(values);
+        
+        return dows.size == 7
+            && dows.has_key(Calendar.DayOfWeek.SUN)
+            && dows[Calendar.DayOfWeek.SUN] == -1
+            && dows[Calendar.DayOfWeek.MON] == -2
+            && dows[Calendar.DayOfWeek.TUE] == -3
+            && dows[Calendar.DayOfWeek.WED] == -4
+            && dows[Calendar.DayOfWeek.THU] == -5
+            && dows[Calendar.DayOfWeek.FRI] == -6
+            && dows[Calendar.DayOfWeek.SAT] == -7;
+    }
+    
     // Checks that an RRULE was generated,
     // the summary is       meeting at work
     // the location is      work
@@ -122,6 +170,14 @@ private class QuickAddRecurring : UnitTest.Harness {
             && !event.rrule.has_duration;
     }
     
+    private bool daily(out string? dump) throws Error {
+        Component.Event event;
+        return basic("meeting at work daily at 10am", out event, out dump)
+            && event.rrule.is_daily
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration;
+    }
+    
     private bool every_day_10_days(out string? dump) throws Error {
         Component.Event event;
         return basic("meeting at work every day at 10am for 10 days", out event, out dump)
@@ -181,6 +237,8 @@ private class QuickAddRecurring : UnitTest.Harness {
             if (!Component.RecurrenceRule.decode_day(value, out dow, out position))
                 return false;
             
+            debug("value %d -> %s %d", value, (dow != null) ? dow.to_string() : "null", position);
+            
             if (!by_days.has_key(dow) || by_days.get(dow) != position)
                 return false;
         }
@@ -201,6 +259,59 @@ private class QuickAddRecurring : UnitTest.Harness {
             && check_byrule_day(event, by_days);
     }
     
+    private bool tuesday_weekly(out string? dump) throws Error {
+        Gee.Map<Calendar.DayOfWeek?, int> by_days = iterate<Calendar.DayOfWeek?>(
+            Calendar.DayOfWeek.TUE).to_hash_map_as_keys<int>(dow => 0);
+        
+        Component.Event event;
+        return basic("meeting at work tuesday at 10am weekly", out event, out dump)
+            && event.rrule.is_weekly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.TUE)
+            && check_byrule_day(event, by_days);
+    }
+    
+    private bool weekdays_to_1pm(out string? dump) throws Error {
+        Gee.Map<Calendar.DayOfWeek?, int> by_days = from_array<Calendar.DayOfWeek?>(
+            Calendar.DayOfWeek.weekdays).to_hash_map_as_keys<int>(dow => 0);
+        
+        Component.Event event;
+        return basic("meeting at work weekdays from 10am to 1pm", out event, out dump)
+            && event.rrule.is_weekly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && by_days.keys.contains(event.exact_time_span.start_date.day_of_week)
+            && event.exact_time_span.end_exact_time.to_wall_time().equal_to(new Calendar.WallTime(13, 0, 0))
+            && check_byrule_day(event, by_days);
+    }
+    
+    private bool weekends(out string? dump) throws Error {
+        Gee.Map<Calendar.DayOfWeek?, int> by_days = from_array<Calendar.DayOfWeek?>(
+            Calendar.DayOfWeek.weekend_days).to_hash_map_as_keys<int>(dow => 0);
+        
+        Component.Event event;
+        return basic("meeting weekends at work at 10am", out event, out dump)
+            && event.rrule.is_weekly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && by_days.keys.contains(event.exact_time_span.start_date.day_of_week)
+            && check_byrule_day(event, by_days);
+    }
+    
+    private bool every_weekend(out string? dump) throws Error {
+        Gee.Map<Calendar.DayOfWeek?, int> by_days = from_array<Calendar.DayOfWeek?>(
+            Calendar.DayOfWeek.weekend_days).to_hash_map_as_keys<int>(dow => 0);
+        
+        Component.Event event;
+        return basic("meeting at work every weekend at 10am", out event, out dump)
+            && event.rrule.is_weekly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && by_days.keys.contains(event.exact_time_span.start_date.day_of_week)
+            && check_byrule_day(event, by_days);
+    }
+    
     private bool every_tuesday_thursday(out string? dump) throws Error {
         Gee.Map<Calendar.DayOfWeek?, int> by_days = iterate<Calendar.DayOfWeek?>(
             Calendar.DayOfWeek.TUE, Calendar.DayOfWeek.THU).to_hash_map_as_keys<int>(dow => 0);
@@ -228,6 +339,20 @@ private class QuickAddRecurring : UnitTest.Harness {
                 || event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.THU))
             && check_byrule_day(event, by_days);
     }
+    
+    private bool every_tuesday_and_thursday_for_3_weeks(out string? dump) throws Error {
+        Gee.Map<Calendar.DayOfWeek?, int> by_days = iterate<Calendar.DayOfWeek?>(
+            Calendar.DayOfWeek.TUE, Calendar.DayOfWeek.THU).to_hash_map_as_keys<int>(dow => 0);
+        
+        Component.Event event;
+        return basic("meeting at work at 10am every tuesday and thursday for 3 weeks", out event, out dump)
+            && event.rrule.is_weekly
+            && event.rrule.interval == 1
+            && (event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.TUE)
+                || event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.THU))
+            && check_byrule_day(event, by_days)
+            && event.rrule.count == 3;
+    }
 }
 
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]