Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Basic due date filtering

due:today
due:future
due:past
  • Loading branch information...
commit 2c56047fdd02292d29d72f4e53b837383e2a43e5 1 parent 5ae7a22
@benrhughes authored
View
4 Client/App.xaml
@@ -20,11 +20,11 @@
<Setter Property="Opacity" Value="0.25" />
</DataTrigger>
<!--Change Style if task due today -->
- <DataTrigger Binding="{Binding Path=IsTaskDue}" Value="0">
+ <DataTrigger Binding="{Binding Path=IsTaskDue}" Value="1">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
<!--Change Style if task overdue -->
- <DataTrigger Binding="{Binding Path=IsTaskDue}" Value="-1">
+ <DataTrigger Binding="{Binding Path=IsTaskDue}" Value="2">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
View
3  Client/Resource.resx
@@ -209,8 +209,9 @@ Filtering
- one free-text filter per line
- filters are AND'd together
- to exclude a term, start the line with a '-', eg -@work
+ - due date filtering: due:today, due:past or due:future
- press Enter twice to save and close the filter
- - optionally case sensitive
+ - set filter case sensistivity under File -&gt; Options...
Copyright 2012 Ben Hughes</value>
</data>
View
1  ToDo.Net.sln
@@ -11,7 +11,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
BSD_LICENSE.txt = BSD_LICENSE.txt
README.md = README.md
- Updates.xml = Updates.xml
EndProjectSection
EndProject
Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "NewInstaller", "NewInstaller\NewInstaller.vdproj", "{5ACB819D-A073-4645-8035-339B28276310}"
View
24 ToDoLib/ExtensionMethods.cs
@@ -26,5 +26,29 @@ public static bool Contains(this string source, string toCheck, StringComparison
{
return source.IndexOf(toCheck, comp) >= 0;
}
+
+ public static bool IsDateGreaterThan(this string dateString, DateTime date)
+ {
+ if (dateString.IsNullOrEmpty())
+ return false;
+
+ DateTime comparisonDate;
+ if (!DateTime.TryParse(dateString, out comparisonDate))
+ return false;
+
+ return comparisonDate.Date > date.Date;
+ }
+
+ public static bool IsDateLessThan(this string dateString, DateTime date)
+ {
+ if (dateString.IsNullOrEmpty())
+ return false;
+
+ DateTime comparisonDate;
+ if (!DateTime.TryParse(dateString, out comparisonDate))
+ return false;
+
+ return comparisonDate.Date < date.Date;
+ }
}
}
View
125 ToDoLib/Task.cs
@@ -6,12 +6,19 @@
namespace ToDoLib
{
+ public enum Due
+ {
+ NotDue,
+ Today,
+ Overdue
+ }
+
public class Task : IComparable
{
const string completedPattern = @"^X\s((\d{4})-(\d{2})-(\d{2}))?";
const string priorityPattern = @"^(?<priority>\([A-Z]\)\s)";
const string createdDatePattern = @"(?<date>(\d{4})-(\d{2})-(\d{2}))";
- const string dueRelativePattern = @"due:(?<dateRelative>today|tomorrow|monday|tuesday|wednesday|thursday|friday|saturday|sunday)";
+ const string dueRelativePattern = @"due:(?<dateRelative>today|tomorrow|monday|tuesday|wednesday|thursday|friday|saturday|sunday)";
const string dueDatePattern = @"due:(?<date>(\d{4})-(\d{2})-(\d{2}))";
const string projectPattern = @"(?<proj>\+[^\s]+)";
const string contextPattern = @"(?<context>\@[^\s]+)";
@@ -49,35 +56,25 @@ public bool Completed
}
}
- /// <summary>
- /// Test task Due
- /// -1 task overDue
- /// 0 task due today
- /// 1 task not due
- /// </summary>
- public int IsTaskDue
- {
- set { }
- get
- {
- if (Completed)
- return 1;
-
- DateTime tmp = new DateTime();
-
- if (DateTime.TryParse(DueDate, out tmp))
- {
- if (tmp < DateTime.Today)
- return -1;
- if (tmp == DateTime.Today)
- return 0;
- return 1;
- }
- else {
- return 1;
- }
- }
- }
+ public Due IsTaskDue
+ {
+ get
+ {
+ if (Completed)
+ return Due.NotDue;
+
+ DateTime tmp = new DateTime();
+
+ if (!DateTime.TryParse(DueDate, out tmp))
+ return Due.NotDue;
+
+ if (tmp < DateTime.Today)
+ return Due.Overdue;
+ if (tmp == DateTime.Today)
+ return Due.Today;
+ return Due.NotDue;
+ }
+ }
// Parsing needs to comply with these rules: https://github.com/ginatrapani/todo.txt-touch/wiki/Todo.txt-File-Format
@@ -86,39 +83,39 @@ public Task(string raw)
{
raw = raw.Replace(Environment.NewLine, ""); //make sure it's just on one line
- //Replace relative days with hard date
- //Supports english: 'today', 'tomorrow', and full weekdays ('monday', 'tuesday', etc)
- //If today is the specified weekday, due date will be in one week
- //TODO implement short weekdays ('mon', 'tue', etc) and other languages
- var reg = new Regex(dueRelativePattern, RegexOptions.IgnoreCase);
- var dueDateRelative = reg.Match(raw).Groups["dateRelative"].Value.Trim();
- if (!dueDateRelative.IsNullOrEmpty())
- {
- var due = new DateTime();
- dueDateRelative = dueDateRelative.ToLower();
- if (dueDateRelative == "today")
- {
- due = DateTime.Now;
- }
- else if (dueDateRelative == "tomorrow")
- {
- due = DateTime.Now.AddDays(1);
- }
- else if (dueDateRelative == "monday" | dueDateRelative == "tuesday" | dueDateRelative == "wednesday" | dueDateRelative == "thursday" | dueDateRelative == "friday" | dueDateRelative == "saturday" | dueDateRelative == "sunday")
- {
- due = DateTime.Now;
- //if day of week, add days to today until weekday matches input
- //if today is the specified weekday, due date will be in one week
- do
- {
- due = due.AddDays(1);
- } while (!string.Equals(due.ToString("dddd"), dueDateRelative, StringComparison.CurrentCultureIgnoreCase));
- }
- raw = reg.Replace(raw, "due:" + due.ToString("yyyy-MM-dd"));
- }
-
- //Set Raw string after replacing relative date but before removing matches
- Raw = raw;
+ //Replace relative days with hard date
+ //Supports english: 'today', 'tomorrow', and full weekdays ('monday', 'tuesday', etc)
+ //If today is the specified weekday, due date will be in one week
+ //TODO implement short weekdays ('mon', 'tue', etc) and other languages
+ var reg = new Regex(dueRelativePattern, RegexOptions.IgnoreCase);
+ var dueDateRelative = reg.Match(raw).Groups["dateRelative"].Value.Trim();
+ if (!dueDateRelative.IsNullOrEmpty())
+ {
+ var due = new DateTime();
+ dueDateRelative = dueDateRelative.ToLower();
+ if (dueDateRelative == "today")
+ {
+ due = DateTime.Now;
+ }
+ else if (dueDateRelative == "tomorrow")
+ {
+ due = DateTime.Now.AddDays(1);
+ }
+ else if (dueDateRelative == "monday" | dueDateRelative == "tuesday" | dueDateRelative == "wednesday" | dueDateRelative == "thursday" | dueDateRelative == "friday" | dueDateRelative == "saturday" | dueDateRelative == "sunday")
+ {
+ due = DateTime.Now;
+ //if day of week, add days to today until weekday matches input
+ //if today is the specified weekday, due date will be in one week
+ do
+ {
+ due = due.AddDays(1);
+ } while (!string.Equals(due.ToString("dddd"), dueDateRelative, StringComparison.CurrentCultureIgnoreCase));
+ }
+ raw = reg.Replace(raw, "due:" + due.ToString("yyyy-MM-dd"));
+ }
+
+ //Set Raw string after replacing relative date but before removing matches
+ Raw = raw;
// because we are removing matches as we go, the order we process is important. It must be:
// - completed
@@ -128,7 +125,7 @@ public Task(string raw)
// - projects | contexts
// What we have left is the body
- reg = new Regex(completedPattern, RegexOptions.IgnoreCase);
+ reg = new Regex(completedPattern, RegexOptions.IgnoreCase);
var s = reg.Match(raw).Value.Trim();
if (string.IsNullOrEmpty(s))
View
447 ToDoLib/TaskList.cs
@@ -3,225 +3,236 @@
using System.Linq;
using System.Text;
using System.IO;
+using System.Text.RegularExpressions;
namespace ToDoLib
{
- /// <summary>
- /// A thin data access abstraction over the actual todo.txt file
- /// </summary>
- public class TaskList
- {
- // It may look like an overly simple approach has been taken here, but it's well considered. This class
- // represents *the file itself* - when you call a method it should be as though you directly edited the file.
- // This reduces the liklihood of concurrent update conflicts by making each action as autonomous as possible.
- // Although this does lead to some extra IO, it's a small price for maintaining the integrity of the file.
-
- // NB, this is not the place for higher-level functions like searching, task manipulation etc. It's simply
- // for CRUDing the todo.txt file.
-
- List<Task> _tasks = null;
- string _filePath = null;
-
- public List<Task> Tasks { get { return _tasks; } }
-
- public TaskList(string filePath)
- {
- _filePath = filePath;
- ReloadTasks();
- }
-
- public void ReloadTasks()
- {
-
- Log.Debug("Loading tasks from {0}", _filePath);
-
- try
- {
- _tasks = new List<Task>();
- foreach (var line in File.ReadAllLines(_filePath))
- _tasks.Add(new Task(line));
-
- Log.Debug("Finished loading tasks from {0}", _filePath);
- }
- catch (IOException ex)
- {
- var msg = "There was a problem trying to read from your todo.txt file";
- Log.Error(msg, ex);
- throw new TaskException(msg, ex);
- }
- catch (Exception ex)
- {
- Log.Error(ex.ToString());
- throw;
- }
- }
-
- public void Add(Task task)
- {
- try
- {
- var output = task.ToString();
-
- Log.Debug("Adding task '{0}'", output);
-
- var text = File.ReadAllText(_filePath);
- if (text.Length > 0 && !text.EndsWith(Environment.NewLine))
- output = Environment.NewLine + output;
-
- File.AppendAllLines(_filePath, new string[] { output });
-
- Log.Debug("Task '{0}' added", output);
-
- ReloadTasks();
- }
- catch (IOException ex)
- {
- var msg = "An error occurred while trying to add your task to the task list file";
- Log.Error(msg, ex);
- throw new TaskException(msg, ex);
- }
- catch (Exception ex)
- {
- Log.Error(ex.ToString());
- throw;
- }
-
- }
-
- public void Delete(Task task)
- {
- try
- {
- Log.Debug("Deleting task '{0}'", task.ToString());
-
- ReloadTasks(); // make sure we're working on the latest file
-
- if (_tasks.Remove(_tasks.First(t => t.Raw == task.Raw)))
- File.WriteAllLines(_filePath, _tasks.Select(t => t.ToString()));
-
- Log.Debug("Task '{0}' deleted", task.ToString());
-
- ReloadTasks();
- }
- catch (IOException ex)
- {
- var msg = "An error occurred while trying to remove your task from the task list file";
- Log.Error(msg, ex);
- throw new TaskException(msg, ex);
- }
- catch (Exception ex)
- {
- Log.Error(ex.ToString());
- throw;
- }
- }
-
-
- public void Update(Task currentTask, Task newTask)
- {
- try
- {
- Log.Debug("Updating task '{0}' to '{1}'", currentTask.ToString(), newTask.ToString());
-
- ReloadTasks();
- var currentIndex = _tasks.IndexOf(_tasks.First(t => t.Raw == currentTask.Raw));
-
- _tasks[currentIndex] = newTask;
-
- File.WriteAllLines(_filePath, _tasks.Select(t => t.ToString()));
-
- Log.Debug("Task '{0}' updated", currentTask.ToString());
-
- ReloadTasks();
- }
- catch (IOException ex)
- {
- var msg = "An error occurred while trying to update your task int the task list file";
- Log.Error(msg, ex);
- throw new TaskException(msg, ex);
- }
- catch (Exception ex)
- {
- Log.Error(ex.ToString());
- throw;
- }
- }
-
- public IEnumerable<Task> Sort(SortType sort, bool FilterCaseSensitive, string Filter)
- {
- return SortList(sort, FilterList(_tasks, FilterCaseSensitive, Filter));
- }
-
- public static List<Task> FilterList(List<Task> tasks, bool FilterCaseSensitive, string Filter)
- {
- var comparer = FilterCaseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase;
-
- if (String.IsNullOrEmpty(Filter)) return tasks;
-
- List<Task> tasksFilter = new List<Task>();
-
- foreach (var task in tasks)
- {
- bool include = true;
- foreach (var filter in Filter.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
- {
- if (filter.Substring(0, 1) != "-")
- { // if the filter does not start with a minus and filter is contained in task then filter out
- if (!task.Raw.Contains(filter, comparer))
- include = false;
- }
- else
- { // if the filter starts with a minus then (ignoring the minus) check if the filter is contained in the task then filter out if so
- if (task.Raw.Contains(filter.Substring(1), comparer))
- include = false;
- }
- }
-
- if (include)
- tasksFilter.Add(task);
- }
- return tasksFilter;
- }
-
- public static IEnumerable<Task> SortList(SortType sort, List<Task> tasks)
- {
- Log.Debug("Sorting {0} tasks by {1}", tasks.Count().ToString(), sort.ToString());
-
- switch (sort)
- {
- // nb, we sub-sort by completed for most sorts by prepending either a or z
- case SortType.Completed:
- return tasks.OrderBy(t => t.Completed);
- case SortType.Context:
- return tasks.OrderBy(t =>
- {
- var s = t.Completed ? "z" : "a";
- if (t.Contexts != null && t.Contexts.Count > 0)
- s += t.Contexts.Min().Substring(1);
- else
- s += "zzz";
- return s;
- });
- case SortType.Alphabetical:
- return tasks.OrderBy(t => (t.Completed ? "z" : "a") + t.Raw);
- case SortType.DueDate:
- return tasks.OrderBy(t => (t.Completed ? "z" : "a") + (string.IsNullOrEmpty(t.DueDate) ? "9999-99-99" : t.DueDate));
- case SortType.Priority:
- return tasks.OrderBy(t => (t.Completed ? "z" : "a") + (string.IsNullOrEmpty(t.Priority) ? "(z)" : t.Priority));
- case SortType.Project:
- return tasks.OrderBy(t =>
- {
- var s = t.Completed ? "z" : "a";
- if (t.Projects != null && t.Projects.Count > 0)
- s += t.Projects.Min().Substring(1);
- else
- s += "zzz";
- return s;
- });
- case SortType.None:
- default:
- return tasks;
- }
- }
- }
+ /// <summary>
+ /// A thin data access abstraction over the actual todo.txt file
+ /// </summary>
+ public class TaskList
+ {
+ // It may look like an overly simple approach has been taken here, but it's well considered. This class
+ // represents *the file itself* - when you call a method it should be as though you directly edited the file.
+ // This reduces the liklihood of concurrent update conflicts by making each action as autonomous as possible.
+ // Although this does lead to some extra IO, it's a small price for maintaining the integrity of the file.
+
+ // NB, this is not the place for higher-level functions like searching, task manipulation etc. It's simply
+ // for CRUDing the todo.txt file.
+
+ List<Task> _tasks = null;
+ string _filePath = null;
+
+ public List<Task> Tasks { get { return _tasks; } }
+
+ public TaskList(string filePath)
+ {
+ _filePath = filePath;
+ ReloadTasks();
+ }
+
+ public void ReloadTasks()
+ {
+
+ Log.Debug("Loading tasks from {0}", _filePath);
+
+ try
+ {
+ _tasks = new List<Task>();
+ foreach (var line in File.ReadAllLines(_filePath))
+ _tasks.Add(new Task(line));
+
+ Log.Debug("Finished loading tasks from {0}", _filePath);
+ }
+ catch (IOException ex)
+ {
+ var msg = "There was a problem trying to read from your todo.txt file";
+ Log.Error(msg, ex);
+ throw new TaskException(msg, ex);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex.ToString());
+ throw;
+ }
+ }
+
+ public void Add(Task task)
+ {
+ try
+ {
+ var output = task.ToString();
+
+ Log.Debug("Adding task '{0}'", output);
+
+ var text = File.ReadAllText(_filePath);
+ if (text.Length > 0 && !text.EndsWith(Environment.NewLine))
+ output = Environment.NewLine + output;
+
+ File.AppendAllLines(_filePath, new string[] { output });
+
+ Log.Debug("Task '{0}' added", output);
+
+ ReloadTasks();
+ }
+ catch (IOException ex)
+ {
+ var msg = "An error occurred while trying to add your task to the task list file";
+ Log.Error(msg, ex);
+ throw new TaskException(msg, ex);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex.ToString());
+ throw;
+ }
+
+ }
+
+ public void Delete(Task task)
+ {
+ try
+ {
+ Log.Debug("Deleting task '{0}'", task.ToString());
+
+ ReloadTasks(); // make sure we're working on the latest file
+
+ if (_tasks.Remove(_tasks.First(t => t.Raw == task.Raw)))
+ File.WriteAllLines(_filePath, _tasks.Select(t => t.ToString()));
+
+ Log.Debug("Task '{0}' deleted", task.ToString());
+
+ ReloadTasks();
+ }
+ catch (IOException ex)
+ {
+ var msg = "An error occurred while trying to remove your task from the task list file";
+ Log.Error(msg, ex);
+ throw new TaskException(msg, ex);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex.ToString());
+ throw;
+ }
+ }
+
+
+ public void Update(Task currentTask, Task newTask)
+ {
+ try
+ {
+ Log.Debug("Updating task '{0}' to '{1}'", currentTask.ToString(), newTask.ToString());
+
+ ReloadTasks();
+ var currentIndex = _tasks.IndexOf(_tasks.First(t => t.Raw == currentTask.Raw));
+
+ _tasks[currentIndex] = newTask;
+
+ File.WriteAllLines(_filePath, _tasks.Select(t => t.ToString()));
+
+ Log.Debug("Task '{0}' updated", currentTask.ToString());
+
+ ReloadTasks();
+ }
+ catch (IOException ex)
+ {
+ var msg = "An error occurred while trying to update your task int the task list file";
+ Log.Error(msg, ex);
+ throw new TaskException(msg, ex);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex.ToString());
+ throw;
+ }
+ }
+
+ public IEnumerable<Task> Sort(SortType sort, bool FilterCaseSensitive, string Filter)
+ {
+ return SortList(sort, FilterList(_tasks, FilterCaseSensitive, Filter));
+ }
+
+ public static List<Task> FilterList(List<Task> tasks, bool FilterCaseSensitive, string Filter)
+ {
+ var comparer = FilterCaseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase;
+
+ if (String.IsNullOrEmpty(Filter))
+ return tasks;
+
+ List<Task> tasksFilter = new List<Task>();
+
+ foreach (var task in tasks)
+ {
+ bool include = true;
+ foreach (var filter in Filter.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
+ {
+ if (filter.Equals("due:today", StringComparison.OrdinalIgnoreCase)
+ && task.DueDate == DateTime.Now.ToString("yyyy-MM-dd"))
+ continue;
+ else if (filter.Equals("due:future", StringComparison.OrdinalIgnoreCase)
+ && task.DueDate.IsDateGreaterThan(DateTime.Now))
+ continue;
+ else if (filter.Equals("due:past", StringComparison.OrdinalIgnoreCase)
+ && task.DueDate.IsDateLessThan(DateTime.Now))
+ continue;
+
+ if (filter.Substring(0, 1) == "-")
+ {
+ if (task.Raw.Contains(filter.Substring(1), comparer))
+ include = false;
+ }
+ else if (!task.Raw.Contains(filter, comparer))
+ {
+ include = false;
+ }
+ }
+
+ if (include)
+ tasksFilter.Add(task);
+ }
+ return tasksFilter;
+ }
+
+ public static IEnumerable<Task> SortList(SortType sort, List<Task> tasks)
+ {
+ Log.Debug("Sorting {0} tasks by {1}", tasks.Count().ToString(), sort.ToString());
+
+ switch (sort)
+ {
+ // nb, we sub-sort by completed for most sorts by prepending either a or z
+ case SortType.Completed:
+ return tasks.OrderBy(t => t.Completed);
+ case SortType.Context:
+ return tasks.OrderBy(t =>
+ {
+ var s = t.Completed ? "z" : "a";
+ if (t.Contexts != null && t.Contexts.Count > 0)
+ s += t.Contexts.Min().Substring(1);
+ else
+ s += "zzz";
+ return s;
+ });
+ case SortType.Alphabetical:
+ return tasks.OrderBy(t => (t.Completed ? "z" : "a") + t.Raw);
+ case SortType.DueDate:
+ return tasks.OrderBy(t => (t.Completed ? "z" : "a") + (string.IsNullOrEmpty(t.DueDate) ? "9999-99-99" : t.DueDate));
+ case SortType.Priority:
+ return tasks.OrderBy(t => (t.Completed ? "z" : "a") + (string.IsNullOrEmpty(t.Priority) ? "(z)" : t.Priority));
+ case SortType.Project:
+ return tasks.OrderBy(t =>
+ {
+ var s = t.Completed ? "z" : "a";
+ if (t.Projects != null && t.Projects.Count > 0)
+ s += t.Projects.Min().Substring(1);
+ else
+ s += "zzz";
+ return s;
+ });
+ case SortType.None:
+ default:
+ return tasks;
+ }
+ }
+ }
}
View
29 ToDoTests/TaskListTests.cs
@@ -180,10 +180,31 @@ public void TestFilterExclude()
Assert.AreEqual(TaskList.FilterList(tl, false, "-XXXXXX").Count, 4);
}
- /*
- ,
- ,
- ,/**/
+ [Test]
+ public void Filter_due_today()
+ {
+ var t = getTestList();
+ t.Add(new Task("Oh my due:today"));
+
+ Assert.AreEqual(TaskList.FilterList(t, false, "due:today").Count, 1);
+ }
+
+ [Test]
+ public void Filter_due_future()
+ {
+ var t = getTestList();
+ t.Add(new Task("Oh my due:tomorrow"));
+
+ Assert.AreEqual(TaskList.FilterList(t, false, "due:future").Count, 1);
+ }
+
+ [Test]
+ public void Filter_due_past()
+ {
+ var t = getTestList();
+
+ Assert.AreEqual(TaskList.FilterList(t, false, "due:past").Count, 3);
+ }
[Test]
public void TestSortAlphabetical()
View
3  ToDoTests/testtasks.txt
@@ -0,0 +1,3 @@
+A task
+(B) Add_ToCollection +test @task
+x 2012-06-05 Update_InCollection +test @task

1 comment on commit 2c56047

@benrhughes
Owner

Closes #79

Please sign in to comment.
Something went wrong with that request. Please try again.