Commit 5a1b86cc authored by Rohit Kaushik's avatar Rohit Kaushik

todo-txt: add subtask implementation

This patch allows indenting task line as subtask using 4 initial
spaces. First all the tasks are parsed and indention stored,
after which the subtask heirarchy is resolved using indent
level.

Some heuristics:
    1) The difference between current line indent and previous
    line indent cannot exceed 1, if it does, the difference is
    taken 1.

    2) Tabs should not be used for indenting subtask. If tabs are
    present the parsing is stopped and a warning generated.
parent 0a78ae99
Pipeline #17759 passed with stage
in 8 minutes and 40 seconds
......@@ -92,6 +92,7 @@ print_task (GString *output,
GDateTime *creation_dt;
const gchar *description;
gint priority;
gint depth;
gboolean is_complete;
is_complete = gtd_task_get_complete (task);
......@@ -101,6 +102,11 @@ print_task (GString *output,
description = gtd_task_get_description (task);
creation_dt = gtd_task_get_creation_date (task);
completion_dt = gtd_task_get_completion_date (task);
depth = gtd_task_get_depth (task);
/* First add spaces for indentation */
if (depth > 0)
g_string_append_printf (output, "%*c", depth * INDENT_LEN, ' ');
if (is_complete)
g_string_append (output, "x ");
......@@ -191,7 +197,10 @@ update_source (GtdProviderTodoTxt *self)
list = g_ptr_array_index (self->cache, i);
tasks = gtd_task_list_get_tasks (list);
/* And now each task */
/* First sort the tasks */
tasks = g_list_sort (tasks, (GCompareFunc) gtd_task_compare);
/* And now save each task */
for (l = tasks; l; l = l->next)
print_task (contents, l->data);
}
......@@ -285,14 +294,20 @@ parse_list_colors_line (GtdProviderTodoTxt *self,
}
static void
parse_task (GtdProviderTodoTxt *self,
const gchar *line)
parse_task (GtdProviderTodoTxt *self,
const gchar *line,
GHashTable *list_to_tasks,
GError **error)
{
g_autofree gchar *list_name = NULL;
GtdTaskList *list;
GPtrArray *tasks;
GtdTask *task;
task = gtd_todo_txt_parser_parse_task (GTD_PROVIDER (self), line, &list_name);
task = gtd_todo_txt_parser_parse_task (GTD_PROVIDER (self), line, &list_name, error);
if (!task)
GTD_RETURN ();
/*
* Create the list if it doesn't exist yet; this might happen with todo.txt files
......@@ -317,6 +332,13 @@ parse_task (GtdProviderTodoTxt *self,
list = g_hash_table_lookup (self->lists, list_name);
}
/* Add to the temporary GPtrArray that will be consumed below */
if (!g_hash_table_contains (list_to_tasks, list))
g_hash_table_insert (list_to_tasks, list, g_ptr_array_new ());
tasks = g_hash_table_lookup (list_to_tasks, list);
g_ptr_array_add (tasks, task);
gtd_task_set_list (task, list);
gtd_task_list_save_task (list, task);
......@@ -349,7 +371,11 @@ remove_empty_lines (GStrv lines)
{
gchar *line;
line = g_strstrip (lines[i]);
/*
* We can only remove the trailing space here since leading
* whitespace determine the indentation and subtasks
*/
line = g_strchomp (lines[i]);
if (!line || line[0] == '\n' || line[0] == '\0')
continue;
......@@ -360,9 +386,68 @@ remove_empty_lines (GStrv lines)
return g_steal_pointer (&valid_lines);
}
static void
resolve_subtasks (GtdProviderTodoTxt *self,
GHashTable *list_to_tasks)
{
GtdTaskList *list;
GPtrArray *tasks;
GHashTableIter iter;
GQueue tasks_stack;
g_queue_init (&tasks_stack);
g_hash_table_iter_init (&iter, list_to_tasks);
while (g_hash_table_iter_next (&iter, (gpointer) &list, (gpointer) &tasks))
{
gint64 previous_indent;
guint i;
previous_indent = 0;
GTD_TRACE_MSG ("Setting up tasklist '%s'", gtd_task_list_get_name (list));
for (i = 0; tasks && i < tasks->len; i++)
{
GtdTask *parent_task;
GtdTask *task;
guint indent;
guint j;
task = g_ptr_array_index (tasks, i);
parent_task = NULL;
indent = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (task), "indent"));
GTD_TRACE_MSG (" Adding task '%s' (%s)",
gtd_task_get_title (task),
gtd_object_get_uid (GTD_OBJECT (task)));
/* If the indent changed, remove from the difference in level from stack */
for (j = 0; j <= previous_indent - indent; j++)
g_queue_pop_head (&tasks_stack);
parent_task = g_queue_peek_head (&tasks_stack);
if (parent_task)
gtd_task_add_subtask (parent_task, task);
gtd_task_set_list (task, list);
gtd_task_list_save_task (list, task);
g_queue_push_head (&tasks_stack, task);
previous_indent = indent;
}
/* Clear the queue since we're changing projects */
g_queue_clear (&tasks_stack);
}
}
static void
reload_tasks (GtdProviderTodoTxt *self)
{
g_autoptr (GHashTable) list_to_tasks = NULL;
g_autofree gchar *input_path = NULL;
g_autofree gchar *file_contents = NULL;
g_autoptr (GPtrArray) valid_lines = NULL;
......@@ -401,12 +486,15 @@ reload_tasks (GtdProviderTodoTxt *self)
{
g_autoptr (GError) line_error = NULL;
GtdTodoTxtLineType line_type;
const gchar *line;
gchar *line;
guint line_number;
line_number = n_lines - vtable_len + i;
line = g_ptr_array_index (valid_lines, line_number);
/* Since leading whitespace doesn't matter here, remove them */
line = g_strstrip (line);
line_type = gtd_todo_txt_parser_get_line_type (line, &line_error);
if (line_error)
......@@ -425,6 +513,12 @@ reload_tasks (GtdProviderTodoTxt *self)
}
}
/* First, create all the tasks and store them temporarily in a GPtrArray */
list_to_tasks = g_hash_table_new_full (g_direct_hash,
g_direct_equal,
NULL,
(GDestroyNotify) g_ptr_array_unref);
/* Then regular task lines */
for (i = 0; i < n_lines - vtable_len; i++)
{
......@@ -454,7 +548,7 @@ reload_tasks (GtdProviderTodoTxt *self)
break;
case GTD_TODO_TXT_LINE_TYPE_TASK:
parse_task (self, line);
parse_task (self, line, list_to_tasks, &line_error);
break;
case GTD_TODO_TXT_LINE_TYPE_LIST_COLORS:
......@@ -466,8 +560,18 @@ reload_tasks (GtdProviderTodoTxt *self)
default:
break;
}
if (line_error)
g_warning ("Error parsing line %d: %s", i + 1, line_error->message);
}
/*
* Now that all the tasks are created and properly stored in a GPtrArray,
* we have to go through each GPtrArray, sort it, and figure out the parent
* and children relationship between the tasks.
*/
resolve_subtasks (self, list_to_tasks);
GTD_EXIT;
}
......
......@@ -220,10 +220,55 @@ tokenize_line (const gchar *line,
return tokens;
}
/**
* get_line_indentation:
* @line: the tasklist line to be parsed
*
* Parses indentation level of a task line
*
* Returns: indentation level or -1 if error
*/
static gint
get_line_indentation (const gchar *line,
GError **error)
{
guint indent_level = 0;
guint leading_space = 0;
guint i;
GTD_ENTRY;
for (i = 0; line[i] != '\0'; i++)
{
if (line[i] == ' ')
{
leading_space += 1;
}
else if (line[i] == '\t')
{
g_set_error (error,
GTD_TODO_TXT_PARSER_ERROR,
GTD_TODO_TXT_PARSER_INVALID_INDENT,
"Invalid tabs found in task indentation");
GTD_RETURN (-1);
}
else
{
break;
}
}
indent_level = leading_space / INDENT_LEN;
GTD_RETURN (indent_level);
}
GtdTask*
gtd_todo_txt_parser_parse_task (GtdProvider *provider,
const gchar *line,
gchar **out_list_name)
gchar **out_list_name,
GError **error)
{
g_autoptr (GString) parent_task_name = NULL;
g_autoptr (GString) list_name = NULL;
......@@ -234,6 +279,7 @@ gtd_todo_txt_parser_parse_task (GtdProvider *provider,
g_auto (GStrv) tokens = NULL;
GDateTime *dt;
Token token_id;
gint indent;
guint i;
dt = NULL;
......@@ -243,6 +289,10 @@ gtd_todo_txt_parser_parse_task (GtdProvider *provider,
parent_task_name = g_string_new (NULL);
state.last_token = TOKEN_START;
state.in_description = FALSE;
indent = get_line_indentation (line, error);
if (indent == -1)
return NULL;
task = GTD_TASK (gtd_provider_todo_txt_generate_task (GTD_PROVIDER_TODO_TXT (provider)));
tokens = tokenize_line (line, " ");
......@@ -312,6 +362,7 @@ gtd_todo_txt_parser_parse_task (GtdProvider *provider,
gtd_task_set_title (task, title->str);
gtd_task_set_description (task, note->str);
g_object_set_data (G_OBJECT (task), "indent", GINT_TO_POINTER (indent));
if (out_list_name)
*out_list_name = g_strdup (list_name->str + 1);
......
......@@ -19,6 +19,8 @@
#ifndef GTD_TODO_TXT_PARSE_H
#define GTD_TODO_TXT_PARSE_H
#define INDENT_LEN 4
#include "gnome-todo.h"
#include "gtd-task-todo-txt.h"
......@@ -30,6 +32,7 @@ typedef enum
{
GTD_TODO_TXT_PARSER_INVALID_DUE_DATE,
GTD_TODO_TXT_PARSER_INVALID_COLOR_HEX,
GTD_TODO_TXT_PARSER_INVALID_INDENT,
GTD_TODO_TXT_PARSER_INVALID_LINE,
GTD_TODO_TXT_PARSER_UNSUPPORTED_TOKEN,
GTD_TODO_TXT_PARSER_WRONG_LINE_TYPE,
......@@ -56,7 +59,8 @@ GPtrArray* gtd_todo_txt_parser_parse_task_lists (GtdProvider
GtdTask* gtd_todo_txt_parser_parse_task (GtdProvider *provider,
const gchar *line,
gchar **out_list_name);
gchar **out_list_name,
GError **error);
gboolean gtd_todo_txt_parser_parse_task_list_color (GHashTable *name_to_tasklist,
const gchar *line,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment