[gnome-todo] todo-txt: Rework parser



commit 40c825e09d0784d0ce5f9074814123a906ec9936
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Sun Feb 4 22:37:56 2018 -0200

    todo-txt: Rework parser
    
    This commit introduces an improved and more robust
    Todo.txt parser, but has one major drawback that we
    have to figure out before the release: the Todo.txt
    provider stopped supporting parent/child tasks.

 plugins/todo-txt/gtd-plugin-todo-txt.c   | 217 ++++++------
 plugins/todo-txt/gtd-provider-todo-txt.c | 543 ++++++++++++++++---------------
 plugins/todo-txt/gtd-todo-txt-parser.c   | 537 +++++++++++++-----------------
 plugins/todo-txt/gtd-todo-txt-parser.h   |  42 +--
 4 files changed, 644 insertions(+), 695 deletions(-)
---
diff --git a/plugins/todo-txt/gtd-plugin-todo-txt.c b/plugins/todo-txt/gtd-plugin-todo-txt.c
index e0cc350..cf66be9 100644
--- a/plugins/todo-txt/gtd-plugin-todo-txt.c
+++ b/plugins/todo-txt/gtd-plugin-todo-txt.c
@@ -18,6 +18,7 @@
 
 #define G_LOG_DOMAIN "GtdPluginTodoTxt"
 
+#include "gtd-debug.h"
 #include "gtd-plugin-todo-txt.h"
 #include "gtd-provider-todo-txt.h"
 
@@ -53,88 +54,29 @@ enum
 
 static void          gtd_activatable_iface_init                  (GtdActivatableInterface  *iface);
 
-G_DEFINE_DYNAMIC_TYPE_EXTENDED (GtdPluginTodoTxt, gtd_plugin_todo_txt, PEAS_TYPE_EXTENSION_BASE,
-                                0,
-                                G_IMPLEMENT_INTERFACE_DYNAMIC (GTD_TYPE_ACTIVATABLE,
-                                                               gtd_activatable_iface_init))
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (GtdPluginTodoTxt, gtd_plugin_todo_txt, PEAS_TYPE_EXTENSION_BASE, 0,
+                                G_IMPLEMENT_INTERFACE_DYNAMIC (GTD_TYPE_ACTIVATABLE, 
gtd_activatable_iface_init))
 
-/*
- * GtdActivatable interface implementation
- */
-static void
-gtd_plugin_todo_txt_activate (GtdActivatable *activatable)
-{
-  ;
-}
-
-static void
-gtd_plugin_todo_txt_deactivate (GtdActivatable *activatable)
-{
-  ;
-}
-
-static GList*
-gtd_plugin_todo_txt_get_header_widgets (GtdActivatable *activatable)
-{
-  return NULL;
-}
-
-static GtkWidget*
-gtd_plugin_todo_txt_get_preferences_panel (GtdActivatable *activatable)
-{
-  GtdPluginTodoTxt *plugin = GTD_PLUGIN_TODO_TXT (activatable);
-
-  return plugin->preferences_box;
-
-}
-
-static GList*
-gtd_plugin_todo_txt_get_panels (GtdActivatable *activatable)
-{
-  return NULL;
-}
-
-static GList*
-gtd_plugin_todo_txt_get_providers (GtdActivatable *activatable)
-{
-  GtdPluginTodoTxt *plugin = GTD_PLUGIN_TODO_TXT (activatable);
-  return plugin->providers;
-}
-
-static void
-gtd_activatable_iface_init (GtdActivatableInterface *iface)
-{
-  iface->activate = gtd_plugin_todo_txt_activate;
-  iface->deactivate = gtd_plugin_todo_txt_deactivate;
-  iface->get_header_widgets = gtd_plugin_todo_txt_get_header_widgets;
-  iface->get_preferences_panel = gtd_plugin_todo_txt_get_preferences_panel;
-  iface->get_panels = gtd_plugin_todo_txt_get_panels;
-  iface->get_providers = gtd_plugin_todo_txt_get_providers;
-}
 
 /*
- * Init
+ * Auxiliary methods
  */
 
 static gboolean
-gtd_plugin_todo_txt_set_default_source (GtdPluginTodoTxt *self)
+set_default_source (GtdPluginTodoTxt *self)
 {
-  g_autofree gchar *default_file;
-  GError *error;
+  g_autofree gchar *default_file = NULL;
+  g_autoptr (GError) error = NULL;
 
-  error = NULL;
-  default_file = g_build_filename (g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS),
-                                   "todo.txt",
-                                   NULL);
+  GTD_ENTRY;
+
+  default_file = g_build_filename (g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS), "todo.txt", NULL);
   self->source_file = g_file_new_for_path (default_file);
 
   if (g_file_query_exists (self->source_file, NULL))
-    return TRUE;
+    GTD_RETURN (TRUE);
 
-  g_file_create (self->source_file,
-                     G_FILE_CREATE_NONE,
-                     NULL,
-                     &error);
+  g_file_create (self->source_file, G_FILE_CREATE_NONE, NULL, &error);
 
   if (error)
     {
@@ -143,39 +85,35 @@ gtd_plugin_todo_txt_set_default_source (GtdPluginTodoTxt *self)
                                       error->message,
                                       NULL,
                                       NULL);
-
-      g_clear_error (&error);
-      return FALSE;
+      GTD_RETURN (FALSE);
     }
 
-  return TRUE;
+  GTD_RETURN (TRUE);
 }
 
 static gboolean
-gtd_plugin_todo_txt_set_source (GtdPluginTodoTxt *self)
+setup_source (GtdPluginTodoTxt *self)
 {
-  GError *error;
-  gchar  *source;
+  g_autoptr (GError) error = NULL;
+  g_autofree gchar *source = NULL;
+
+  GTD_ENTRY;
 
-  error = NULL;
   source = g_settings_get_string (self->settings, "file");
 
   if (!source || source[0] == '\0')
     {
-      if (!gtd_plugin_todo_txt_set_default_source (self))
-        return FALSE;
+      if (!set_default_source (self))
+        GTD_RETURN (FALSE);
     }
   else
     {
-      self->source_file = g_file_new_for_uri (source);
+      self->source_file = g_file_new_for_path (source);
     }
 
   if (!g_file_query_exists (self->source_file, NULL))
     {
-      g_file_create (self->source_file,
-                     G_FILE_CREATE_NONE,
-                     NULL,
-                     &error);
+      g_file_create (self->source_file, G_FILE_CREATE_NONE, NULL, &error);
 
       if (error)
         {
@@ -184,46 +122,52 @@ gtd_plugin_todo_txt_set_source (GtdPluginTodoTxt *self)
                                           error->message,
                                           NULL,
                                           NULL);
-
-          g_clear_error (&error);
-          return FALSE;
+          GTD_RETURN (FALSE);
         }
     }
 
-  return TRUE;
+  GTD_RETURN (TRUE);
 }
 
+
+/*
+ * Callbacks
+ */
+
 static void
-gtd_plugin_todo_txt_source_changed_finished_cb (GtdPluginTodoTxt *self)
+on_source_changed_finished_cb (GtdPluginTodoTxt *self)
 {
   GtdProviderTodoTxt *provider;
   gboolean set;
 
-  set = gtd_plugin_todo_txt_set_source (self);
+  GTD_ENTRY;
+
+  set = setup_source (self);
 
   if (!set)
-    return;
+    GTD_RETURN ();
 
   provider = gtd_provider_todo_txt_new (self->source_file);
   self->providers = g_list_append (self->providers, provider);
 
   g_signal_emit_by_name (self, "provider-added", provider);
+
+  GTD_EXIT;
 }
 
 static void
-gtd_plugin_todo_txt_source_changed_cb (GtkWidget *preference_panel,
-                                       gpointer   user_data)
+on_source_changed_cb (GtkWidget        *preference_panel,
+                      GtdPluginTodoTxt *self)
 {
-  GtdPluginTodoTxt *self;
   GtdProviderTodoTxt *provider;
 
-  self = GTD_PLUGIN_TODO_TXT (user_data);
+  GTD_ENTRY;
 
   g_clear_object (&self->source_file);
 
   g_settings_set_string (self->settings,
                         "file",
-                         gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (self->preferences)));
+                         gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (self->preferences)));
 
   if (self->providers)
     {
@@ -235,9 +179,72 @@ gtd_plugin_todo_txt_source_changed_cb (GtkWidget *preference_panel,
       g_signal_emit_by_name (self, "provider-removed", provider);
     }
 
-  gtd_plugin_todo_txt_source_changed_finished_cb (self);
+  on_source_changed_finished_cb (self);
+
+  GTD_EXIT;
+}
+
+
+/*
+ * GtdActivatable implementation
+ */
+
+static void
+gtd_plugin_todo_txt_activate (GtdActivatable *activatable)
+{
+  ;
 }
 
+static void
+gtd_plugin_todo_txt_deactivate (GtdActivatable *activatable)
+{
+  ;
+}
+
+static GList*
+gtd_plugin_todo_txt_get_header_widgets (GtdActivatable *activatable)
+{
+  return NULL;
+}
+
+static GtkWidget*
+gtd_plugin_todo_txt_get_preferences_panel (GtdActivatable *activatable)
+{
+  GtdPluginTodoTxt *plugin = GTD_PLUGIN_TODO_TXT (activatable);
+
+  return plugin->preferences_box;
+
+}
+
+static GList*
+gtd_plugin_todo_txt_get_panels (GtdActivatable *activatable)
+{
+  return NULL;
+}
+
+static GList*
+gtd_plugin_todo_txt_get_providers (GtdActivatable *activatable)
+{
+  GtdPluginTodoTxt *plugin = GTD_PLUGIN_TODO_TXT (activatable);
+  return plugin->providers;
+}
+
+static void
+gtd_activatable_iface_init (GtdActivatableInterface *iface)
+{
+  iface->activate = gtd_plugin_todo_txt_activate;
+  iface->deactivate = gtd_plugin_todo_txt_deactivate;
+  iface->get_header_widgets = gtd_plugin_todo_txt_get_header_widgets;
+  iface->get_preferences_panel = gtd_plugin_todo_txt_get_preferences_panel;
+  iface->get_panels = gtd_plugin_todo_txt_get_panels;
+  iface->get_providers = gtd_plugin_todo_txt_get_providers;
+}
+
+
+/*
+ * GObject overrides
+ */
+
 static void
 gtd_plugin_todo_txt_finalize (GObject *object)
 {
@@ -272,12 +279,10 @@ gtd_plugin_todo_txt_class_init (GtdPluginTodoTxtClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-  object_class->finalize     = gtd_plugin_todo_txt_finalize;
+  object_class->finalize = gtd_plugin_todo_txt_finalize;
   object_class->get_property = gtd_plugin_todo_txt_get_property;
 
-  g_object_class_override_property (object_class,
-                                    PROP_PREFERENCES_PANEL,
-                                    "preferences-panel");
+  g_object_class_override_property (object_class, PROP_PREFERENCES_PANEL, "preferences-panel");
 }
 
 static void
@@ -288,7 +293,7 @@ gtd_plugin_todo_txt_init (GtdPluginTodoTxt *self)
   gboolean   set;
 
   self->settings = g_settings_new ("org.gnome.todo.plugins.todo-txt");
-  set = gtd_plugin_todo_txt_set_source (self);
+  set = setup_source (self);
   self->providers = NULL;
 
   if (set)
@@ -317,13 +322,9 @@ gtd_plugin_todo_txt_init (GtdPluginTodoTxt *self)
 
   gtk_widget_show_all (self->preferences_box);
 
-  g_signal_connect (self->preferences,
-                    "file-set",
-                    G_CALLBACK (gtd_plugin_todo_txt_source_changed_cb),
-                    self);
+  g_signal_connect (self->preferences, "file-set", G_CALLBACK (on_source_changed_cb), self);
 }
 
-/* Empty class_finalize method */
 static void
 gtd_plugin_todo_txt_class_finalize (GtdPluginTodoTxtClass *klass)
 {
diff --git a/plugins/todo-txt/gtd-provider-todo-txt.c b/plugins/todo-txt/gtd-provider-todo-txt.c
index 4f0808c..54bebdb 100644
--- a/plugins/todo-txt/gtd-provider-todo-txt.c
+++ b/plugins/todo-txt/gtd-provider-todo-txt.c
@@ -1,6 +1,7 @@
 /* gtd-provider-todo-txt.c
  *
- * Copyright (C) 2016 Rohit Kaushik <kaushikrohit325 gmail com>
+ * Copyright © 2016 Rohit Kaushik <kaushikrohit325 gmail com>
+ *             2018 Georges Basile Stavracas Neto <georges stavracas gmail com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -18,6 +19,7 @@
 
 #define G_LOG_DOMAIN "GtdProviderTodoTxt"
 
+#include "gtd-debug.h"
 #include "gtd-provider-todo-txt.h"
 #include "gtd-plugin-todo-txt.h"
 #include "gtd-todo-txt-parser.h"
@@ -29,7 +31,7 @@
 
 struct _GtdProviderTodoTxt
 {
-  GtdObject          parent;
+  GtdObject           parent;
 
   GIcon              *icon;
 
@@ -41,16 +43,24 @@ struct _GtdProviderTodoTxt
 
   GList              *task_lists;
   GPtrArray          *cache;
+
+  guint64             task_counter;
   gboolean            should_reload;
 };
 
+static void          on_file_monitor_changed_cb                  (GFileMonitor       *monitor,
+                                                                  GFile              *first,
+                                                                  GFile              *second,
+                                                                  GFileMonitorEvent   event,
+                                                                  GtdProviderTodoTxt *self);
+
 static void          gtd_provider_iface_init                     (GtdProviderInterface *iface);
 
 G_DEFINE_TYPE_WITH_CODE (GtdProviderTodoTxt, gtd_provider_todo_txt, GTD_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (GTD_TYPE_PROVIDER,
-                                                gtd_provider_iface_init))
+                         G_IMPLEMENT_INTERFACE (GTD_TYPE_PROVIDER, gtd_provider_iface_init))
 
-enum {
+enum
+{
   PROP_0,
   PROP_DEFAULT_TASKLIST,
   PROP_DESCRIPTION,
@@ -62,266 +72,277 @@ enum {
   LAST_PROP
 };
 
+
 /*
- * GtdProviderInterface implementation
+ * Auxiliary methods
  */
-static const gchar*
-gtd_provider_todo_txt_get_id (GtdProvider *provider)
-{
-  return "todo-txt";
-}
-
-static const gchar*
-gtd_provider_todo_txt_get_name (GtdProvider *provider)
-{
-  return _("Todo.txt");
-}
-
-static const gchar*
-gtd_provider_todo_txt_get_description (GtdProvider *provider)
-{
-  return _("On the Todo.txt file");
-}
-
 
-static gboolean
-gtd_provider_todo_txt_get_enabled (GtdProvider *provider)
-{
-  return TRUE;
-}
-
-static GIcon*
-gtd_provider_todo_txt_get_icon (GtdProvider *provider)
+static void
+print_task (GString *output,
+            GtdTask *task)
 {
-  GtdProviderTodoTxt *self;
+  GtdTaskList *list;
+  GDateTime *dt;
+  GtdTask *parent;
+  const gchar *list_name;
+  const gchar *title;
+  gint priority;
+  gboolean is_complete;
+
+  is_complete = gtd_task_get_complete (task);
+  title = gtd_task_get_title (task);
+  priority = gtd_task_get_priority (task);
+  dt = gtd_task_get_due_date (task);
+  list = gtd_task_get_list (task);
+  parent = gtd_task_get_parent (task);
+
+  list_name = gtd_task_list_get_name (list);
+
+  if (is_complete)
+    g_string_append (output, "x ");
+
+  if (priority)
+    {
+      if (priority == 1)
+        g_string_append (output, "(C) ");
+      else if (priority == 2)
+        g_string_append (output, "(B) ");
+      else if (priority == 3)
+        g_string_append (output, "(A) ");
+    }
 
-  self = GTD_PROVIDER_TODO_TXT (provider);
+  g_string_append_printf (output, "%s @%s", title, list_name);
 
-  return self->icon;
-}
+  if (dt)
+    {
+      g_autofree gchar *formatted_time = g_date_time_format (dt, "%F");
+      g_string_append_printf (output, " due:%s", formatted_time);
+    }
 
-static void
-emit_generic_error (GError *error)
-{
-  g_warning ("%s: %s: %s",
-             G_STRFUNC,
-             "Error while opening Todo.txt",
-             error->message);
-
-  gtd_manager_emit_error_message (gtd_manager_get_default (),
-                                  _("Error while opening Todo.txt"),
-                                  error->message,
-                                  NULL,
-                                  NULL);
+  g_string_append (output, "\n");
 }
 
 static void
 update_source (GtdProviderTodoTxt *self)
 {
-
-  GFileOutputStream *write_stream;
-  GDataOutputStream *writer;
+  g_autofree gchar *output_path = NULL;
+  g_autoptr (GString) contents = NULL;
+  g_autoptr (GError) error = NULL;
   GtdTaskList *list;
-  GError *error;
-  GList *tasks, *l;
   guint i;
 
-  error = NULL;
-  tasks = NULL;
-  l = NULL;
-  self->should_reload = FALSE;
-
-  write_stream = g_file_replace (self->source_file,
-                                 NULL,
-                                 TRUE,
-                                 G_FILE_CREATE_NONE,
-                                 NULL,
-                                 &error);
-  if (error)
-    {
-      emit_generic_error (error);
-      g_error_free (error);
-      return;
-    }
+  GTD_ENTRY;
 
-  writer = g_data_output_stream_new (G_OUTPUT_STREAM (write_stream));
+  contents = g_string_new ("");
 
   for (i = 0; i < self->cache->len; i++)
     {
-      gchar *list_line;
+      g_autofree gchar *color_str = NULL;
+      g_autoptr (GdkRGBA) color = NULL;
+      GList *tasks, *l;
+
       list = g_ptr_array_index (self->cache, i);
 
       tasks = gtd_task_list_get_tasks (list);
-      tasks = g_list_sort (tasks, (GCompareFunc) gtd_task_compare);
 
-      list_line = gtd_todo_txt_parser_serialize_list (list);
+      /* Print the list as the first line */
+      color = gtd_task_list_get_color (list);
+      color_str = gdk_rgba_to_string (color);
 
-      g_data_output_stream_put_string (writer,
-                                       list_line,
-                                       NULL,
-                                       NULL);
+      g_string_append_printf (contents, "@%s color:%s\n",
+                              gtd_task_list_get_name (list),
+                              color_str);
 
+      /* And now each task */
       for (l = tasks; l != NULL; l = l->next)
-        {
-          gchar *task_line;
+        print_task (contents, l->data);
+    }
+
+  output_path = g_file_get_path (self->source_file);
+  g_file_set_contents (output_path, contents->str, contents->len, &error);
+
+  if (error)
+    {
+      g_warning ("Error saving Todo.txt file: %s", error->message);
+      return;
+    }
 
-          task_line = gtd_todo_txt_parser_serialize_task (l->data);
+  self->should_reload = FALSE;
 
-          g_data_output_stream_put_string (writer,
-                                           task_line,
-                                           NULL,
-                                           NULL);
+  GTD_EXIT;
+}
 
-          g_free (task_line);
-        }
+static void
+add_task_list (GtdProviderTodoTxt *self,
+               GtdTaskList        *list)
+{
+  if (g_hash_table_contains (self->lists, gtd_task_list_get_name (list)))
+    return;
 
-      g_free (list_line);
-    }
+  g_ptr_array_add (self->cache, list);
+  g_hash_table_insert (self->lists, g_strdup (gtd_task_list_get_name (list)), list);
 
-  g_output_stream_close (G_OUTPUT_STREAM (writer), NULL, NULL);
-  g_output_stream_close (G_OUTPUT_STREAM (write_stream), NULL, NULL);
+  self->task_lists = g_list_append (self->task_lists, list);
 }
 
-static GtdTaskList*
-create_list (GtdProviderTodoTxt *self,
-             gchar              *name)
+static void
+parse_task_list (GtdProviderTodoTxt *self,
+                 const gchar        *line)
 {
-  GtdTaskList *task_list;
+  g_autoptr (GtdTaskList) list = NULL;
 
-  if (g_hash_table_contains (self->lists, name))
-    return g_hash_table_lookup (self->lists, name);
+  list = gtd_todo_txt_parser_parse_task_list (GTD_PROVIDER (self), line);
 
-  task_list = gtd_task_list_new (GTD_PROVIDER (self));
-  gtd_task_list_set_is_removable (task_list, TRUE);
-  g_ptr_array_add (self->cache, task_list);
-  g_hash_table_insert (self->lists, g_strdup (name), task_list);
-  gtd_task_list_set_name (task_list, name);
-  self->task_lists = g_list_append (self->task_lists, task_list);
+  if (!list)
+    return;
 
-  return task_list;
+  add_task_list (self, g_steal_pointer (&list));
 }
 
 static void
-gtd_provider_todo_txt_load_tasks (GtdProviderTodoTxt *self)
+parse_task (GtdProviderTodoTxt *self,
+            const gchar        *line)
 {
-  GFileInputStream *readstream;
-  GDataInputStream *reader;
+  g_autofree gchar *list_name = NULL;
   GtdTaskList *list;
-  GtdTask *parent_task;
   GtdTask *task;
-  GError *error;
-  GList *tokens;
-  gboolean valid;
-  gchar *line_read;
-  gchar *list_name;
-  gchar *root_task_name;
 
-  g_return_if_fail (G_IS_FILE (self->source_file));
+  task = gtd_todo_txt_parser_parse_task (GTD_PROVIDER (self), line, &list_name);
 
-  tokens = NULL;
-  error = NULL;
-  readstream = g_file_read (self->source_file, NULL, &error);
+  /*
+   * Create the list if it doesn't exist yet; this might happen with todo.txt files
+   * that are not saved from GNOME To Do.
+   */
+  if (!g_hash_table_contains (self->lists, list_name))
+    {
+      GTD_TRACE_MSG ("Creating new list with name '%s'", list_name);
+
+      list = g_object_new (GTD_TYPE_TASK_LIST,
+                           "provider", self,
+                           "name", list_name,
+                           "is-removable", TRUE,
+                           NULL);
+
+      add_task_list (self, list);
+    }
+  else
+    {
+      GTD_TRACE_MSG ("List with name '%s' already exists, reusing", list_name);
+
+      list = g_hash_table_lookup (self->lists, list_name);
+    }
+
+  gtd_task_set_list (task, list);
+  gtd_task_list_save_task (list, task);
+
+  g_hash_table_insert (self->tasks, (gpointer) gtd_object_get_uid (GTD_OBJECT (task)), task);
+  self->task_counter++;
+}
+
+static void
+reload_tasks (GtdProviderTodoTxt *self)
+{
+  g_autofree gchar *input_path = NULL;
+  g_autofree gchar *file_contents = NULL;
+  g_autoptr (GError) error = NULL;
+  g_auto (GStrv) lines = NULL;
+  guint i;
+
+  GTD_ENTRY;
+
+  input_path = g_file_get_path (self->source_file);
+
+  g_debug ("Reading the contents of %s", input_path);
+
+  g_file_get_contents (input_path, &file_contents, NULL, &error);
 
   if (error)
     {
-      emit_generic_error (error);
-      g_error_free (error);
+      g_warning ("Error reading Todo.txt file: %s", error->message);
       return;
     }
 
+  self->task_counter = 0;
 
-  reader = g_data_input_stream_new (G_INPUT_STREAM (readstream));
+  lines = g_strsplit (file_contents, "\n", -1);
 
-  while (!error)
+  for (i = 0; lines && lines[i]; i++)
     {
-      line_read = g_data_input_stream_read_line (reader, NULL, NULL, &error);
+      GtdTodoTxtLineType line_type;
+      gchar *line;
+
+      line = lines[i];
+
+      /* Last element of the array is NULL */
+      if (!line || line[0] == '\0')
+        break;
+
+      g_strstrip (line);
+
+      GTD_TRACE_MSG ("Parsing line %d: %s", i, line);
+
+      line_type = gtd_todo_txt_parser_get_line_type (line, &error);
 
       if (error)
         {
-          g_warning ("%s: %s: %s",
-                     G_STRFUNC,
-                     "Error while reading a line from Todo.txt",
-                     error->message);
-
-          gtd_manager_emit_error_message (gtd_manager_get_default (),
-                                          _("Error while reading a line from Todo.txt"),
-                                          error->message,
-                                          NULL,
-                                          NULL);
-          g_error_free (error);
-
+          g_warning ("Error parsing line %d: %s", i + 1, error->message);
+          g_clear_error (&error);
           continue;
         }
 
-      if (!line_read)
-        break;
-
-      g_strstrip (line_read);
-      tokens = gtd_todo_txt_parser_tokenize (line_read);
-      valid = gtd_todo_txt_parser_validate_token_format (tokens);
-
-      if (valid)
+      switch (line_type)
         {
-          if (g_list_length (tokens) == 1)
-            {
-              list_name = &((gchar*)tokens->data)[0];
-              list_name++;
-              create_list (self, list_name);
-              continue;
-            }
-
-          task = gtd_provider_generate_task (GTD_PROVIDER (self));
-          gtd_todo_txt_parser_parse_tokens (task, tokens);
-
-          g_hash_table_insert (self->tasks, g_strdup (gtd_task_get_title (task)), task);
-
-          list = create_list (self, g_object_get_data (G_OBJECT (task), "list_name"));
-          gtd_task_set_list (task, list);
-
-          if (g_object_get_data (G_OBJECT (task), "root_task_name"))
-            {
-              root_task_name = g_object_get_data (G_OBJECT (task), "root_task_name");
-
-              if (g_hash_table_contains (self->tasks, root_task_name))
-                {
-                  parent_task =  g_hash_table_lookup (self->tasks, root_task_name);
-                }
-              else
-                {
-                  parent_task = gtd_provider_generate_task (GTD_PROVIDER (self));
-                  gtd_task_set_list (parent_task, list);
-                  gtd_task_set_title (parent_task, g_object_get_data (G_OBJECT (task), "root_task_name"));
-
-                  g_hash_table_insert (self->tasks, root_task_name, parent_task);
-                }
-
-              gtd_task_add_subtask (parent_task, task);
-              gtd_task_list_save_task (list, parent_task);
-            }
-
-          gtd_task_list_save_task (list, task);
+        case GTD_TODO_TXT_LINE_TYPE_TASKLIST:
+          parse_task_list (self, line);
+          break;
+
+        case GTD_TODO_TXT_LINE_TYPE_TASK:
+          parse_task (self, line);
+          break;
         }
+    }
+
+  GTD_EXIT;
+}
+
+static void
+setup_file_monitor (GtdProviderTodoTxt *self)
+{
+  g_autoptr (GError) error = NULL;
 
-      g_list_free_full (tokens, g_free);
-      g_free (line_read);
+  self->monitor = g_file_monitor_file (self->source_file,
+                                       G_FILE_MONITOR_WATCH_MOVES,
+                                       NULL,
+                                       &error);
+
+  if (error)
+    {
+      gtd_manager_emit_error_message (gtd_manager_get_default (),
+                                      _("Error while opening the file monitor. Todo.txt will not be 
monitored"),
+                                      error->message,
+                                      NULL,
+                                      NULL);
+      return;
     }
 
-  g_input_stream_close (G_INPUT_STREAM (reader), NULL, NULL);
-  g_input_stream_close (G_INPUT_STREAM (readstream), NULL, NULL);
+  g_signal_connect (self->monitor, "changed", G_CALLBACK (on_file_monitor_changed_cb), self);
 }
 
+
+/*
+ * Callbacks
+ */
+
 static void
-gtd_provider_todo_txt_reload (GFileMonitor       *monitor,
-                              GFile              *first,
-                              GFile              *second,
-                              GFileMonitorEvent   event,
-                              GtdProviderTodoTxt *self)
+on_file_monitor_changed_cb (GFileMonitor       *monitor,
+                            GFile              *first,
+                            GFile              *second,
+                            GFileMonitorEvent   event,
+                            GtdProviderTodoTxt *self)
 {
-  GList *l;
+  GList *l = NULL;
   guint i;
 
-  l = NULL;
-
   if (!self->should_reload)
     {
       self->should_reload = TRUE;
@@ -336,12 +357,13 @@ gtd_provider_todo_txt_reload (GFileMonitor       *monitor,
     g_signal_emit_by_name (self, "list-removed", l->data);
 
   g_list_free (self->task_lists);
+
   self->task_lists = NULL;
   self->lists = g_hash_table_new ((GHashFunc) g_str_hash, (GEqualFunc) g_str_equal);
   self->tasks = g_hash_table_new ((GHashFunc) g_str_hash, (GEqualFunc) g_str_equal);
   self->cache = g_ptr_array_new ();
 
-  gtd_provider_todo_txt_load_tasks (self);
+  reload_tasks (self);
 
   for (i = 0; i < self->cache->len; i++)
     {
@@ -351,28 +373,41 @@ gtd_provider_todo_txt_reload (GFileMonitor       *monitor,
     }
 }
 
-static void
-gtd_provider_todo_txt_load_source_monitor (GtdProviderTodoTxt *self)
+
+/*
+ * GtdProviderInterface implementation
+ */
+
+static const gchar*
+gtd_provider_todo_txt_get_id (GtdProvider *provider)
 {
-  GError *error = NULL;
+  return "todo-txt";
+}
 
-  self->monitor = g_file_monitor_file (self->source_file,
-                                       G_FILE_MONITOR_WATCH_MOVES,
-                                       NULL,
-                                       &error);
+static const gchar*
+gtd_provider_todo_txt_get_name (GtdProvider *provider)
+{
+  return _("Todo.txt");
+}
 
-  if (error)
-    {
-      gtd_manager_emit_error_message (gtd_manager_get_default (),
-                                      _("Error while opening the file monitor. Todo.txt will not be 
monitored"),
-                                      error->message,
-                                      NULL,
-                                      NULL);
-      g_clear_error (&error);
-      return;
-    }
+static const gchar*
+gtd_provider_todo_txt_get_description (GtdProvider *provider)
+{
+  return _("On the Todo.txt file");
+}
 
-  g_signal_connect (self->monitor, "changed", G_CALLBACK (gtd_provider_todo_txt_reload), self);
+static gboolean
+gtd_provider_todo_txt_get_enabled (GtdProvider *provider)
+{
+  return TRUE;
+}
+
+static GIcon*
+gtd_provider_todo_txt_get_icon (GtdProvider *provider)
+{
+  GtdProviderTodoTxt *self = GTD_PROVIDER_TODO_TXT (provider);
+
+  return self->icon;
 }
 
 static void
@@ -381,14 +416,7 @@ gtd_provider_todo_txt_create_task (GtdProvider   *provider,
                                    GCancellable  *cancellable,
                                    GError       **error)
 {
-  GtdProviderTodoTxt *self;
-
-  self = GTD_PROVIDER_TODO_TXT (provider);
-
-  g_return_if_fail (GTD_IS_TASK (task));
-  g_return_if_fail (GTD_IS_TASK_LIST (gtd_task_get_list (task)));
-
-  update_source (self);
+  update_source (GTD_PROVIDER_TODO_TXT (provider));
 }
 
 static void
@@ -397,15 +425,7 @@ gtd_provider_todo_txt_update_task (GtdProvider   *provider,
                                    GCancellable  *cancellable,
                                    GError       **error)
 {
-  GtdProviderTodoTxt *self;
-
-  self = GTD_PROVIDER_TODO_TXT (provider);
-
-  g_return_if_fail (GTD_IS_TASK (task));
-  g_return_if_fail (GTD_IS_TASK_LIST (gtd_task_get_list (task)));
-  g_return_if_fail (G_IS_FILE (self->source_file));
-
-  update_source (self);
+  update_source (GTD_PROVIDER_TODO_TXT (provider));
 }
 
 static void
@@ -414,15 +434,7 @@ gtd_provider_todo_txt_remove_task (GtdProvider   *provider,
                                    GCancellable  *cancellable,
                                    GError       **error)
 {
-  GtdProviderTodoTxt *self;
-
-  self = GTD_PROVIDER_TODO_TXT (provider);
-
-  g_return_if_fail (GTD_IS_TASK (task));
-  g_return_if_fail (GTD_IS_TASK_LIST (gtd_task_get_list (task)));
-  g_return_if_fail (G_IS_FILE (self->source_file));
-
-  update_source (self);
+  update_source (GTD_PROVIDER_TODO_TXT (provider));
 }
 
 static void
@@ -471,11 +483,7 @@ gtd_provider_todo_txt_remove_task_list (GtdProvider   *provider,
                                         GCancellable  *cancellable,
                                         GError       **error)
 {
-  GtdProviderTodoTxt *self;
-
-  self = GTD_PROVIDER_TODO_TXT (provider);
-
-  g_return_if_fail (GTD_IS_TASK_LIST (list));
+  GtdProviderTodoTxt *self = GTD_PROVIDER_TODO_TXT (provider);
 
   g_ptr_array_remove (self->cache, list);
   self->task_lists = g_list_remove (self->task_lists, list);
@@ -488,9 +496,7 @@ gtd_provider_todo_txt_remove_task_list (GtdProvider   *provider,
 static GList*
 gtd_provider_todo_txt_get_task_lists (GtdProvider *provider)
 {
-  GtdProviderTodoTxt *self;
-
-  self = GTD_PROVIDER_TODO_TXT (provider);
+  GtdProviderTodoTxt *self = GTD_PROVIDER_TODO_TXT (provider);
 
   return self->task_lists;
 }
@@ -508,6 +514,17 @@ gtd_provider_todo_txt_set_default_task_list (GtdProvider *provider,
   /* FIXME: implement me */
 }
 
+static GtdTask*
+gtd_provider_todo_txt_generate_task (GtdProvider *provider)
+{
+  GtdProviderTodoTxt *self = (GtdProviderTodoTxt *) provider;
+  g_autofree gchar *uid = NULL;
+
+  uid = g_strdup_printf ("%ld", self->task_counter);
+
+  return g_object_new (GTD_TYPE_TASK, "uid", uid, NULL);
+}
+
 static void
 gtd_provider_iface_init (GtdProviderInterface *iface)
 {
@@ -525,16 +542,13 @@ gtd_provider_iface_init (GtdProviderInterface *iface)
   iface->get_task_lists = gtd_provider_todo_txt_get_task_lists;
   iface->get_default_task_list = gtd_provider_todo_txt_get_default_task_list;
   iface->set_default_task_list = gtd_provider_todo_txt_set_default_task_list;
+  iface->generate_task = gtd_provider_todo_txt_generate_task;
 }
 
-GtdProviderTodoTxt*
-gtd_provider_todo_txt_new (GFile *source_file)
-{
 
-  return g_object_new (GTD_TYPE_PROVIDER_TODO_TXT,
-                       "source", source_file,
-                       NULL);
-}
+/*
+ * GObject overrides
+ */
 
 static void
 gtd_provider_todo_txt_finalize (GObject *object)
@@ -601,8 +615,8 @@ gtd_provider_todo_txt_set_property (GObject      *object,
     {
     case PROP_SOURCE:
       self->source_file = g_value_dup_object (value);
-      gtd_provider_todo_txt_load_source_monitor (self);
-      gtd_provider_todo_txt_load_tasks (self);
+      setup_file_monitor (self);
+      reload_tasks (self);
       break;
 
     default:
@@ -624,7 +638,7 @@ gtd_provider_todo_txt_class_init (GtdProviderTodoTxtClass *klass)
                                    g_param_spec_object ("source",
                                                         "Source file",
                                                         "The Todo.txt source file",
-                                                         G_TYPE_OBJECT,
+                                                        G_TYPE_FILE,
                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
   g_object_class_override_property (object_class, PROP_DEFAULT_TASKLIST, "default-task-list");
@@ -640,11 +654,18 @@ gtd_provider_todo_txt_init (GtdProviderTodoTxt *self)
 {
   gtd_object_set_ready (GTD_OBJECT (self), TRUE);
 
-  self->lists = g_hash_table_new ((GHashFunc) g_str_hash, (GEqualFunc) g_str_equal);
-  self->tasks = g_hash_table_new ((GHashFunc) g_str_hash, (GEqualFunc) g_str_equal);
+  self->lists = g_hash_table_new (g_str_hash, g_str_equal);
+  self->tasks = g_hash_table_new (g_str_hash, g_str_equal);
   self->cache = g_ptr_array_new ();
   self->should_reload = TRUE;
-
-  /* icon */
   self->icon = G_ICON (g_themed_icon_new_with_default_fallbacks ("computer-symbolic"));
 }
+
+GtdProviderTodoTxt*
+gtd_provider_todo_txt_new (GFile *source_file)
+{
+
+  return g_object_new (GTD_TYPE_PROVIDER_TODO_TXT,
+                       "source", source_file,
+                       NULL);
+}
diff --git a/plugins/todo-txt/gtd-todo-txt-parser.c b/plugins/todo-txt/gtd-todo-txt-parser.c
index 808d5a0..a43b919 100644
--- a/plugins/todo-txt/gtd-todo-txt-parser.c
+++ b/plugins/todo-txt/gtd-todo-txt-parser.c
@@ -18,31 +18,30 @@
 
 #define G_LOG_DOMAIN "GtdTodoTxtParser"
 
+#include "gtd-debug.h"
 #include "gtd-todo-txt-parser.h"
 #include "gtd-provider-todo-txt.h"
 
 #include <glib/gi18n.h>
 
-struct _GtdTodoTxtParser
-{
-  GtdObject          parent;
-};
 
-enum
+G_DEFINE_QUARK (GtdTodoTxtParserError, gtd_todo_txt_parser_error)
+
+
+typedef enum
 {
-  TASK_COMPLETE,
-  TASK_PRIORITY,
-  TASK_DATE,
-  TASK_TITLE,
-  TASK_LIST_NAME,
-  ROOT_TASK_NAME,
-  TASK_DUE_DATE
-};
-
-G_DEFINE_TYPE (GtdTodoTxtParser, gtd_todo_txt_parser, GTD_TYPE_OBJECT);
-
-gint
-gtd_todo_txt_parser_get_priority (gchar *token)
+  TOKEN_START,
+  TOKEN_COMPLETE,
+  TOKEN_PRIORITY,
+  TOKEN_DATE,
+  TOKEN_TITLE,
+  TOKEN_LIST_NAME,
+  TOKEN_LIST_COLOR,
+  TOKEN_DUE_DATE
+} Token;
+
+static gint
+parse_priority (const gchar *token)
 {
   switch (token[1])
     {
@@ -62,8 +61,8 @@ gtd_todo_txt_parser_get_priority (gchar *token)
   return 0;
 }
 
-GDateTime*
-gtd_todo_txt_parser_get_date (gchar *token)
+static GDateTime*
+parse_date (const gchar *token)
 {
   GDateTime *dt;
   GDate date;
@@ -87,8 +86,8 @@ gtd_todo_txt_parser_get_date (gchar *token)
   return dt;
 }
 
-gboolean
-gtd_todo_txt_parser_is_date (gchar *dt)
+static gboolean
+is_date (const gchar *dt)
 {
   GDate date;
 
@@ -98,393 +97,321 @@ gtd_todo_txt_parser_is_date (gchar *dt)
   return g_date_valid (&date);
 }
 
-gboolean
-gtd_todo_txt_parser_is_word (gchar *token)
-{
-  guint pos;
-  guint token_length;
-
-  token_length = g_utf8_strlen (token, -1);
-
-  for (pos = 0; pos < token_length; pos++)
-    {
-      if (!g_unichar_isalnum (token[pos]))
-        return FALSE;
-    }
-
-  return TRUE;
-}
-
-gint
-gtd_todo_txt_parser_get_token_id (gchar *token,
-                                  gint last_read)
+static Token
+parse_token_id (const gchar *token,
+                gint         last_read)
 {
   gint token_length;
 
   token_length = strlen (token);
 
   if (!g_strcmp0 (token, "x"))
-    return TASK_COMPLETE;
+    return TOKEN_COMPLETE;
 
   if (token_length == 3 && token[0] == '(' && token[2] == ')')
-    return TASK_PRIORITY;
+    return TOKEN_PRIORITY;
 
-  if (!g_str_has_prefix (token , "due:") && gtd_todo_txt_parser_is_date (token))
-    return TASK_DATE;
+  if (!g_str_has_prefix (token , "due:") && is_date (token))
+    return TOKEN_DATE;
 
-  if (gtd_todo_txt_parser_is_word (token) &&
-      (last_read == TASK_DATE ||
-       last_read == TASK_PRIORITY ||
-       last_read == TASK_COMPLETE||
-       last_read == TASK_TITLE))
-    {
-      return TASK_TITLE;
-    }
+  if (g_str_has_prefix (token , "color:"))
+    return TOKEN_LIST_COLOR;
 
   if (token_length > 1 && token[0] == '@')
-    return TASK_LIST_NAME;
+    return TOKEN_LIST_NAME;
+
+  if (g_str_has_prefix (token , "due:"))
+    return TOKEN_DUE_DATE;
 
-  if (token_length > 1 && token[0] == '+')
-    return ROOT_TASK_NAME;
+  if (last_read == TOKEN_START ||
+      last_read == TOKEN_DATE ||
+      last_read == TOKEN_PRIORITY ||
+      last_read == TOKEN_COMPLETE||
+      last_read == TOKEN_TITLE)
+    {
+      return TOKEN_TITLE;
+    }
+  else if (last_read == TOKEN_LIST_NAME)
+    {
+      return TOKEN_LIST_NAME;
+    }
 
-  if (gtd_todo_txt_parser_is_word (token) && last_read == TASK_LIST_NAME)
-    return TASK_LIST_NAME;
+  return -1;
+}
 
-  if (gtd_todo_txt_parser_is_word (token) && last_read == ROOT_TASK_NAME)
-    return ROOT_TASK_NAME;
+static GStrv
+tokenize_line (const gchar *line)
+{
+  GStrv tokens = NULL;
+  gsize i;
 
-  if (g_str_has_prefix (token , "due:"))
-    return TASK_DUE_DATE;
+  tokens = g_strsplit (line, " ", -1);
 
-  return -1;
+  for (i = 0; tokens && tokens[i]; i++)
+    g_strstrip (tokens[i]);
+
+  return tokens;
 }
 
-void
-gtd_todo_txt_parser_parse_tokens (GtdTask *task,
-                                  GList   *tokens)
+GtdTask*
+gtd_todo_txt_parser_parse_task (GtdProvider  *provider,
+                                const gchar  *line,
+                                gchar       **out_list_name)
 {
+  g_autoptr (GtdTask) task = NULL;
+  g_auto (GStrv) tokens = NULL;
   GDateTime *dt;
   GString *list_name;
   GString *title;
-  GString *root_task_name;
-  GList *l;
+  GString *parent_task_name;
+  Token last_token;
+  Token token_id;
   gboolean is_subtask;
-  gint last_read_token;
-  gint token_id;
+  guint i;
 
-  l = NULL;
   dt = NULL;
   is_subtask = FALSE;
   title = g_string_new (NULL);
   list_name = g_string_new (NULL);
-  root_task_name = g_string_new (NULL);
+  parent_task_name = g_string_new (NULL);
+  last_token = TOKEN_START;
 
-  last_read_token = TASK_COMPLETE;
+  task = gtd_provider_generate_task (provider);
+  tokens = tokenize_line (line);
 
-  for (l = tokens; l != NULL; l = l->next)
+  for (i = 0; tokens && tokens[i]; i++)
     {
+      const gchar *token;
 
-      gchar *str;
-
-      g_strstrip (l->data);
-      str = l->data;
-      token_id = gtd_todo_txt_parser_get_token_id (l->data, last_read_token);
+      token = tokens[i];
+      token_id = parse_token_id (token, last_token);
 
       switch (token_id)
         {
-        case TASK_COMPLETE:
-          last_read_token = TASK_COMPLETE;
+        case TOKEN_COMPLETE:
           gtd_task_set_complete (task, TRUE);
           break;
 
-        case TASK_PRIORITY:
-          last_read_token = TASK_PRIORITY;
-          gtd_task_set_priority (task, gtd_todo_txt_parser_get_priority (l->data));
+        case TOKEN_PRIORITY:
+          last_token = TOKEN_PRIORITY;
+          gtd_task_set_priority (task, parse_priority (token));
           break;
 
-        case TASK_DATE:
-          last_read_token = TASK_DATE;
-          dt = gtd_todo_txt_parser_get_date (l->data);
+        case TOKEN_DATE:
+          dt = parse_date (token);
           break;
 
-        case TASK_TITLE:
-          last_read_token = TASK_TITLE;
-          g_string_append (title, l->data);
+        case TOKEN_TITLE:
+          g_string_append (title, token);
           g_string_append (title, " ");
           break;
 
-        case TASK_LIST_NAME:
-          last_read_token = TASK_LIST_NAME;
-          g_string_append (list_name, l->data);
+        case TOKEN_LIST_NAME:
+          g_string_append (list_name, token);
           g_string_append (list_name, " ");
           break;
 
-        case ROOT_TASK_NAME:
-          last_read_token = ROOT_TASK_NAME;
-          is_subtask = TRUE;
-          g_string_append (root_task_name, l->data);
-          g_string_append (root_task_name, " ");
-          break;
-
-        case TASK_DUE_DATE:
-          last_read_token = TASK_DUE_DATE;
-          dt = gtd_todo_txt_parser_get_date (&str[4]);
+        case TOKEN_DUE_DATE:
+          dt = parse_date (token + strlen ("due:"));
           gtd_task_set_due_date (task, dt);
           break;
 
+        case TOKEN_LIST_COLOR:
+        case TOKEN_START:
         default:
-          return;
+          break;
         }
+
+      last_token = token_id;
     }
 
-  g_strstrip (title->str);
+  g_strstrip (parent_task_name->str);
   g_strstrip (list_name->str);
-  g_strstrip (root_task_name->str);
+  g_strstrip (title->str);
+
   gtd_task_set_title (task, title->str);
-  g_object_set_data_full (G_OBJECT (task), "list_name", g_strdup (list_name->str + 1), g_free);
 
-  if (is_subtask)
-    g_object_set_data_full (G_OBJECT (task), "root_task_name", g_strdup (root_task_name->str + 1), g_free);
+  if (out_list_name)
+    *out_list_name = g_strdup (list_name->str + 1);
 
-  g_string_free (root_task_name, TRUE);
+  g_string_free (parent_task_name, TRUE);
   g_string_free (list_name, TRUE);
   g_string_free (title, TRUE);
+
+  return g_steal_pointer (&task);
 }
 
-gboolean
-gtd_todo_txt_parser_validate_token_format (GList *tokens)
+/**
+ * gtd_todo_txt_parser_parse_task_list:
+ * provider: the @GtdProvider of the new tasklist
+ * @line: the tasklist line to be parsed
+ *
+ * Parses a @GtdTaskList from @line. If there is a 'color:' token,
+ * it is taken into account.
+ *
+ * Returns: (transfer full)(nullable): A @GtdTaskList
+ */
+GtdTaskList*
+gtd_todo_txt_parser_parse_task_list (GtdProvider *provider,
+                                     const gchar *line)
 {
-  GList *it = NULL;
-  gint token_id;
-  gint position = 0;
+  g_autofree gchar *color = NULL;
+  g_auto (GStrv) tokens = NULL;
+  GtdTaskList *new_list;
+  GString *list_name;
+  guint i;
 
-  gboolean complete_tk = FALSE;
-  gboolean priority_tk = FALSE;
-  gboolean task_list_name_tk = FALSE;
+  tokens = tokenize_line (line);
+  list_name = g_string_new (NULL);
 
-  gint last_read = TASK_COMPLETE;
+  GTD_TRACE_MSG ("Parsing tasklist from line '%s'", line);
 
-  for (it = tokens; it != NULL; it = it->next)
+  for (i = 0; tokens && tokens[i]; i++)
     {
-      gchar *str;
+      const gchar *token = tokens[i];
 
-      str = it->data;
-      token_id = gtd_todo_txt_parser_get_token_id (it->data, last_read);
-      position++;
+      if (!token)
+        break;
 
-      switch (token_id)
-        {
-        case TASK_COMPLETE:
-          last_read = TASK_COMPLETE;
-
-          if (position != 1)
-            return FALSE;
-          else
-            complete_tk = TRUE;
+      if (g_str_has_prefix (token, "color:"))
+        color = g_strdup (token + strlen ("color:"));
+      else
+        g_string_append_printf (list_name, "%s ", token[0] == '@' ? token + 1 : token);
+    }
 
-          break;
+  if (list_name->len == 0)
+    {
+      g_string_free (list_name, TRUE);
+      return NULL;
+    }
 
-        case TASK_PRIORITY:
-          last_read = TASK_PRIORITY;
+  g_strstrip (list_name->str);
 
-          if (position != (complete_tk + 1))
-            return FALSE;
-          else
-            priority_tk = TRUE;
+  new_list = g_object_new (GTD_TYPE_TASK_LIST,
+                           "provider", provider,
+                           "name", list_name->str,
+                           "is-removable", TRUE,
+                           NULL);
 
-          break;
+  if (color)
+    {
+      GdkRGBA rgba;
 
-        case TASK_DATE:
-          last_read = TASK_DATE;
+      gdk_rgba_parse (&rgba, color);
 
-          if (position != (complete_tk + priority_tk + 1))
-            return FALSE;
+      gtd_task_list_set_color (new_list, &rgba);
+    }
 
-          if (!gtd_todo_txt_parser_is_date (it->data))
-            {
-              gtd_manager_emit_error_message (gtd_manager_get_default (),
-                                             _("Incorrect date"),
-                                             _("Please make sure the date in Todo.txt is valid."),
-                                             NULL,
-                                             NULL);
-              return FALSE;
-            }
+  g_string_free (list_name, TRUE);
 
-          break;
+  return new_list;
+}
 
-        case TASK_TITLE:
-          last_read = TASK_TITLE;
-          break;
+/**
+ * gtd_todo_txt_parser_get_line_type:
+ * @line: the line to parse
+ * @error: (nullable): return location for a #GError
+ *
+ * Validates the given line and returns the line type.
+ *
+ * Returns: the line type
+ */
+GtdTodoTxtLineType
+gtd_todo_txt_parser_get_line_type (const gchar  *line,
+                                   GError      **error)
+{
+  GtdTodoTxtLineType line_type;
+  g_auto (GStrv) tokens;
+  gboolean task_list_name_tk;
+  Token last_read;
+  Token token_id;
+  gint i;
 
-        case TASK_LIST_NAME:
-          task_list_name_tk = TRUE;
-          last_read = TASK_LIST_NAME;
-          break;
+  GTD_ENTRY;
 
-        case ROOT_TASK_NAME:
-          last_read = ROOT_TASK_NAME;
-          break;
+  tokens = tokenize_line (line);
+  last_read = TOKEN_START;
+  line_type = GTD_TODO_TXT_LINE_TYPE_TASKLIST;
+  task_list_name_tk = FALSE;
 
-        case TASK_DUE_DATE:
-          last_read = TASK_DUE_DATE;
+  for (i = 0; tokens && tokens[i]; i++)
+    {
+      const gchar *token = tokens[i];
 
-          if (!gtd_todo_txt_parser_is_date (&str[4]))
-            return FALSE;
+      token_id = parse_token_id (token, last_read);
 
+      switch (token_id)
+        {
+        case TOKEN_COMPLETE:
+          if (last_read == TOKEN_START)
+            line_type = GTD_TODO_TXT_LINE_TYPE_TASK;
           break;
 
-        default:
-          gtd_manager_emit_error_message (gtd_manager_get_default (),
-                                          _("Unrecognized token in a Todo.txt line"),
-                                          _("To Do cannot recognize some tags in your Todo.txt file. Some 
tasks may not be loaded"),
-                                          NULL,
-                                          NULL);
-          return FALSE;
+        case TOKEN_PRIORITY:
+          if (last_read <= TOKEN_COMPLETE)
+            line_type = GTD_TODO_TXT_LINE_TYPE_TASK;
           break;
-        }
-    }
-
-  if (!task_list_name_tk)
-    {
-      gtd_manager_emit_error_message (gtd_manager_get_default (),
-                                      _("No task list found for some tasks"),
-                                      _("Some of the tasks in your Todo.txt file do not have a task list. To 
Do supports tasks with a task list. Please add a list to all your tasks"),
-                                      NULL,
-                                      NULL);
-      return FALSE;
-    }
 
-  return TRUE;
-}
-
-GList*
-gtd_todo_txt_parser_tokenize (const gchar *line)
-{
-  GList *tokens = NULL;
-  g_autofree GStrv token = NULL;
-  gsize i;
-
-  token = g_strsplit (line, " ", -1);
+        case TOKEN_DATE:
+          if (last_read <= TOKEN_PRIORITY)
+            {
+              line_type = GTD_TODO_TXT_LINE_TYPE_TASK;
 
-  for (i = 0; token[i]; i++)
-    {
-      g_strstrip (token[i]);
-      tokens = g_list_prepend (tokens, g_strdup (token[i]));
-    }
+              if (!is_date (token))
+                {
+                  g_set_error (error,
+                               GTD_TODO_TXT_PARSER_ERROR,
+                               GTD_TODO_TXT_PARSER_INVALID_DUE_DATE,
+                               "Invalid date found");
 
-  tokens = g_list_reverse (tokens);
+                  GTD_RETURN (-1);
+                }
+            }
+          break;
 
-  return tokens;
-}
+        case TOKEN_TITLE:
+          line_type = GTD_TODO_TXT_LINE_TYPE_TASK;
+          break;
 
-gchar*
-gtd_todo_txt_parser_serialize_list (GtdTaskList *list)
-{
-  GString *description;
-  const gchar   *list_name;
+        case TOKEN_LIST_COLOR:
+          break;
 
-  description = g_string_new (NULL);
-  list_name = gtd_task_list_get_name (list);
+        case TOKEN_LIST_NAME:
+          task_list_name_tk = TRUE;
+          break;
 
-  g_string_append (description, "@");
-  g_string_append (description, list_name);
+        case TOKEN_DUE_DATE:
+          line_type = GTD_TODO_TXT_LINE_TYPE_TASK;
 
-  g_string_append (description, "\n");
+          if (!is_date (token + strlen ("due:")))
+            {
+              g_set_error (error,
+                           GTD_TODO_TXT_PARSER_ERROR,
+                           GTD_TODO_TXT_PARSER_INVALID_DUE_DATE,
+                           "Invalid date found");
 
-  return g_string_free (description, FALSE);
-}
+              GTD_RETURN (-1);
+            }
 
-gchar*
-gtd_todo_txt_parser_serialize_task (GtdTask *task)
-{
-  GtdTaskList *list;
-  GDateTime   *dt;
-  GtdTask     *parent;
-  GString     *description;
-  const gchar *list_name;
-  const gchar *title;
-  gint priority;
-  gboolean is_complete;
-
-  description = g_string_new (NULL);
-
-  is_complete = gtd_task_get_complete (task);
-  title = gtd_task_get_title (task);
-  priority = gtd_task_get_priority (task);
-  dt = gtd_task_get_due_date (task);
-  list = gtd_task_get_list (task);
-  parent = gtd_task_get_parent (task);
-
-  list_name = gtd_task_list_get_name (list);
-
-  if (is_complete)
-    g_string_append (description, "x ");
-
-  if (priority)
-    {
-      if (priority == 1)
-        g_string_append (description, "(C) ");
-      else if (priority == 2)
-        g_string_append (description, "(B) ");
-      else if (priority == 3)
-        g_string_append (description, "(A) ");
-    }
+          break;
 
-  g_string_append (description, title);
-  g_string_append (description, " @");
-  g_string_append (description, list_name);
+        case TOKEN_START:
+          /* Nothing */
+          break;
+        }
 
-  if (parent)
-    {
-      g_string_append (description, " +");
-      g_string_append (description, gtd_task_get_title (parent));
+      last_read = token_id;
     }
 
-  if (dt)
+  if (!task_list_name_tk)
     {
-      g_autofree gchar *formatted_time;
-
-      formatted_time = g_date_time_format (dt, "%F");
+      g_set_error (error,
+                   GTD_TODO_TXT_PARSER_ERROR,
+                   GTD_TODO_TXT_PARSER_INVALID_LINE,
+                   "No task list found");
 
-      g_string_append (description, " due:");
-      g_string_append (description, formatted_time);
+      GTD_RETURN (-1);
     }
 
-  g_string_append (description, "\n");
-
-  return g_string_free (description, FALSE);
-}
-
-static void
-gtd_todo_txt_parser_get_property (GObject    *object,
-                            guint       prop_id,
-                            GValue     *value,
-                            GParamSpec *pspec)
-{
-  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-}
-
-static void
-gtd_todo_txt_parser_set_property (GObject      *object,
-                                  guint         prop_id,
-                                  const GValue *value,
-                                  GParamSpec   *pspec)
-{
-  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-}
-
-static void
-gtd_todo_txt_parser_class_init (GtdTodoTxtParserClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-  object_class->get_property = gtd_todo_txt_parser_get_property;
-  object_class->set_property = gtd_todo_txt_parser_set_property;
-
-}
-
-static void
-gtd_todo_txt_parser_init (GtdTodoTxtParser *self)
-{
-  ;
+  GTD_RETURN (line_type);
 }
diff --git a/plugins/todo-txt/gtd-todo-txt-parser.h b/plugins/todo-txt/gtd-todo-txt-parser.h
index 12ccbc4..a6a6669 100644
--- a/plugins/todo-txt/gtd-todo-txt-parser.h
+++ b/plugins/todo-txt/gtd-todo-txt-parser.h
@@ -25,33 +25,33 @@
 
 G_BEGIN_DECLS
 
-#define GTD_TYPE_TODO_TXT_PARSER (gtd_todo_txt_parser_get_type())
+typedef enum
+{
+  GTD_TODO_TXT_PARSER_INVALID_DUE_DATE,
+  GTD_TODO_TXT_PARSER_INVALID_LINE,
+  GTD_TODO_TXT_PARSER_UNSUPPORTED_TOKEN,
+  GTD_TODO_TXT_PARSER_WRONG_LINE_TYPE,
+} GtdTodoTxtParserError;
 
-typedef struct _TaskData TaskData;
+typedef enum
+{
+  GTD_TODO_TXT_LINE_TYPE_TASKLIST,
+  GTD_TODO_TXT_LINE_TYPE_TASK,
+} GtdTodoTxtLineType;
 
-G_DECLARE_FINAL_TYPE (GtdTodoTxtParser, gtd_todo_txt_parser, GTD, TODO_TXT_PARSER, GtdObject)
+#define GTD_TODO_TXT_PARSER_ERROR (gtd_todo_txt_parser_error_quark ())
 
-gint          gtd_todo_txt_parser_get_priority                    (gchar             *token);
+GQuark               gtd_todo_txt_parser_error_quark             (void);
 
-GDateTime*    gtd_todo_txt_parser_get_date                        (gchar             *token);
+GtdTodoTxtLineType   gtd_todo_txt_parser_get_line_type           (const gchar       *line,
+                                                                  GError           **error);
 
-gboolean      gtd_todo_txt_parser_is_date                         (gchar             *dt);
+GtdTaskList*         gtd_todo_txt_parser_parse_task_list         (GtdProvider       *provider,
+                                                                  const gchar       *line);
 
-gboolean      gtd_todo_txt_parser_is_word                         (gchar             *token);
-
-gint          gtd_todo_txt_parser_get_token_id                    (gchar             *token,
-                                                                   gint               last_read);
-
-void          gtd_todo_txt_parser_parse_tokens                    (GtdTask           *task,
-                                                                   GList             *tokens);
-
-gboolean      gtd_todo_txt_parser_validate_token_format           (GList             *tokens);
-
-GList*        gtd_todo_txt_parser_tokenize                        (const gchar       *line);
-
-gchar*        gtd_todo_txt_parser_serialize_list                  (GtdTaskList       *list);
-
-gchar*        gtd_todo_txt_parser_serialize_task                  (GtdTask           *task);
+GtdTask*             gtd_todo_txt_parser_parse_task              (GtdProvider       *provider,
+                                                                  const gchar       *line,
+                                                                  gchar            **out_list_name);
 
 G_END_DECLS
 


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