#include "mdiary.h" static gint64 time_earliest; static gint64 time_latest; /** * @brief The dateFormats struct contains regex/date-info pairs to parse different date formats. */ struct dateFormats { gchar *regex; guint index_count; guint index_year; guint index_month; guint index_day; guint index_hour; guint index_minute; } dateFormats_list[] = { { .regex = "(\\d{1,4})-(\\d{1,2})-(\\d{1,2})\\s*(\\d{1,2}):(\\d{1,2}).*", .index_count = 5, .index_year = 1, .index_month = 2, .index_day = 3, .index_hour = 4, .index_minute = 5 }, { .regex = "(\\d{2,2})\\.(\\d{1,2})\\.(\\d{1,4})\\s*(\\d{1,2}):(\\d{1,2}).*", /* The regex above should begin with (\d{1,2}). But that seems to break the regex... */ .index_count = 5, .index_year = 3, .index_month = 2, .index_day = 1, .index_hour = 4, .index_minute = 5 }, { .regex = "(\\d{1,4})-(\\d{1,2})-(\\d{1,2}).*", .index_count = 3, .index_year = 1, .index_month = 2, .index_day = 3, }, { .regex = "(\\d{1,2})\\.(\\d{1,2})\\.(\\d{1,4}).*", .index_count = 3, .index_year = 3, .index_month = 2, .index_day = 1, }, { .regex = NULL } }; /** * @brief mdiary_get_date_from_string tries to guess the date format used and converts it to GDateTime. * @param Input string. * @return A GDateTime object or NULL on error. You need to free it after use. */ GDateTime *mdiary_get_date_from_string(gchar *string) { return mdiary_get_date_from_string_ext(string, "", ""); } /** * @brief mdiary_get_date_from_string_ext tries to guess the date format used and converts it to GDateTime. * @param Input string. * @return A GDateTime object or NULL on error. You need to free it after use. */ GDateTime *mdiary_get_date_from_string_ext(gchar *string, gchar *prefix, gchar *suffix) { GRegex *regex; GMatchInfo *match_info; struct dateFormats *dateFormats; GDateTime *datetime = NULL; guint year = 0; guint month = 0; guint day = 0; guint hour = 0; guint minute = 0; gchar *regex_string; dateFormats = dateFormats_list; do { regex_string = g_strdup_printf("%s%s%s", prefix, dateFormats->regex, suffix); regex = g_regex_new(regex_string, G_REGEX_RAW, 0, NULL); if (g_regex_match(regex, string, 0, &match_info) && g_regex_get_capture_count(regex) >= dateFormats->index_count && g_match_info_matches(match_info)) { if (dateFormats->index_year) year = atoi(g_match_info_fetch(match_info, dateFormats->index_year)); if (dateFormats->index_month) month = atoi(g_match_info_fetch(match_info, dateFormats->index_month)); if (dateFormats->index_day) day = atoi(g_match_info_fetch(match_info, dateFormats->index_day)); if (dateFormats->index_hour) hour = atoi(g_match_info_fetch(match_info, dateFormats->index_hour)); if (dateFormats->index_minute) minute = atoi(g_match_info_fetch(match_info, dateFormats->index_minute)); if (year >= 0 && month >=1 && month <=12 && day >= 1 && day <= 31 && hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) { datetime = g_date_time_new_local(year, month, day, hour, minute, 0); g_regex_unref(regex); g_free(regex_string); break; } } g_free(regex_string); g_regex_unref(regex); } while ((++dateFormats)->regex); return datetime; } static gchar *mdiary_get_title_from_string(gchar *string) { GRegex *regex; GMatchInfo *match_info; gchar *ret = NULL; regex = g_regex_new("\\# (.*)", G_REGEX_RAW, 0, NULL); if (g_regex_match(regex, string, 0, &match_info) && g_regex_get_capture_count(regex) > 0 && g_match_info_matches(match_info)) { ret = g_strdup(g_match_info_fetch(match_info, 1)); } g_regex_unref(regex); return ret; } static gchar *mdiary_get_summary_from_string(gchar *string) { GRegex *regex; GMatchInfo *match_info; gchar *ret = NULL; regex = g_regex_new("^\\s*Summary: (.*)", G_REGEX_RAW, 0, NULL); if (g_regex_match(regex, string, 0, &match_info) && g_regex_get_capture_count(regex) > 0 && g_match_info_matches(match_info)) { ret = g_strdup(g_match_info_fetch(match_info, 1)); } g_regex_unref(regex); return ret; } static gboolean mdiary_is_empty_line(gchar *string) { GRegex *regex; GMatchInfo *match_info; gboolean ret = 0; regex = g_regex_new("^\\s*$", G_REGEX_RAW, 0, NULL); if (g_regex_match(regex, string, 0, &match_info) && g_match_info_matches(match_info)) { ret = 1; } g_regex_unref(regex); return ret; } static GList *mdiary_add_tags_from_string(gchar *string) { GRegex *regex; GMatchInfo *match_info; GList *ret = NULL; gchar *ptr; gchar *beg_ptr; gchar *orig_ptr; gboolean collected = 0; gchar bak; regex = g_regex_new("^\\s*Tags: (.*)", G_REGEX_RAW, 0, NULL); if (g_regex_match(regex, string, 0, &match_info) && g_regex_get_capture_count(regex) > 0 && g_match_info_matches(match_info)) { /** * TODO: This function should be rewritten to fully use GRegex... */ ptr = beg_ptr = orig_ptr = g_strdup(g_match_info_fetch(match_info, 1)); do { if (*ptr == ',' || *ptr == '\0') { bak = *ptr; if (collected) { collected = 0; *ptr = '\0'; ret = g_list_append(ret, g_strdup(beg_ptr)); beg_ptr = ptr + 1; *ptr = bak; } else { beg_ptr = ptr + 1; } } else if (*ptr == ' ') { if (!collected) beg_ptr = ptr + 1; } else { collected = 1; } } while (*(ptr++)); g_free(orig_ptr); } g_regex_unref(regex); return ret; } static void mdiary_add_file_to_store(gchar *filename, GtkListStore *entryListStore) { GFile *file; GError *err = NULL; GFileInputStream *stream; GDataInputStream *dstream; gchar *line; GDateTime *datetime = NULL; gchar *title = NULL; GList *tagList = NULL; gchar *summary = NULL; GString *text = NULL; guint header_state = 0; g_message("Add file: %s\n", filename); file = g_file_new_for_path(filename); stream = g_file_read(file, NULL, &err); if (err != NULL) { g_error("PARSER: Could not open %s for reading: %s\n", filename, err->message); g_error_free(err); return; } dstream = g_data_input_stream_new(G_INPUT_STREAM(stream)); while (line = g_data_input_stream_read_line(G_DATA_INPUT_STREAM(dstream), NULL, NULL, NULL)) { if (header_state == 0) { if (!datetime) { datetime = mdiary_get_date_from_string_ext(line, "Date: .*", ""); if (datetime) continue; } if (!summary) { summary = mdiary_get_summary_from_string(line); if (summary) continue; } if (!title) { title = mdiary_get_title_from_string(line); if (title) continue; } if (!tagList) { tagList = mdiary_add_tags_from_string(line); if (tagList) continue; } header_state = mdiary_is_empty_line(line); } else if (header_state == 1) { header_state += !mdiary_is_empty_line(line); } if (header_state == 2) { if (text) { text = g_string_append(text, "\n"); text = g_string_append(text, line); } else { text = g_string_new(line); } } } if (!datetime) { g_warning("PARSER: Could not detect date in file!\n"); datetime = g_date_time_new_from_unix_local(0); } if (!tagList) { g_warning("PARSER: Could not detect tags in file!\n"); tagList = g_list_append(tagList, "untagged"); } if (!text) { g_warning("PARSER: Could not find any text in file!\n"); text = g_string_new("No content found."); } if (!summary) { g_warning("PARSER: Could not detect summary in file!\n"); summary = g_strdup("No summary found."); } if (!title) { g_warning("PARSER: Could not detect title in file!\n"); title = g_strdup("Untitled"); } mdiary_add_entry_to_store(entryListStore, title, datetime, tagList, summary, text->str); if (g_date_time_to_unix(datetime) > time_latest) time_latest = g_date_time_to_unix(datetime); if (g_date_time_to_unix(datetime) < time_earliest) time_earliest = g_date_time_to_unix(datetime); g_input_stream_close(G_INPUT_STREAM(dstream), NULL, NULL); g_input_stream_close(G_INPUT_STREAM(stream), NULL, NULL); g_string_free(text, 0); g_free(summary); g_free(title); g_date_time_unref(datetime); g_object_unref(file); } static void mdiary_recurse_and_collect(gchar *base_dir, GtkListStore *entryListStore, guint max_level) { GError *error; GDir *dir = g_dir_open(base_dir, 0, &error); gchar *dirname; gchar *fullPath; GRegex *regex; GMatchInfo *match_info; if (!dir) { g_print("Error, could not open base directory.\n"); } else { while (dirname = (gchar *)g_dir_read_name(dir)) { fullPath = g_strdup_printf("%s/%s", base_dir, dirname); if (g_file_test(fullPath, G_FILE_TEST_IS_REGULAR)) { regex = g_regex_new("\\.md$", G_REGEX_CASELESS, 0, NULL); if (g_regex_match(regex, fullPath, 0, &match_info) && g_match_info_matches(match_info)) mdiary_add_file_to_store(fullPath, entryListStore); g_regex_unref(regex); } else if (g_file_test(fullPath, G_FILE_TEST_IS_DIR) && max_level) { mdiary_recurse_and_collect(fullPath, entryListStore, max_level--); } g_free(fullPath); } g_dir_close(dir); } } /** * @brief mdiary_scan_to_store recursively (max. 5 levels) scans the base_dir into the entryListStore. * @param base_dir The base directory to start scanning in * @param entryListStore Target GtkListStore */ void mdiary_scan_to_store(gchar *base_dir, GtkListStore *entryListStore) { time_earliest = G_MAXINT64; time_latest = 0; mdiary_recurse_and_collect(base_dir, entryListStore, 5); } /** * @brief mdiary_get_time_earliest getter function for time_earliest. Call mdiary_scan_to_store before using. * @return time_earliest */ guint mdiary_get_time_earliest(void) { return time_earliest; } /** * @brief mdiary_get_time_earliest getter function for time_latest. Call mdiary_scan_to_store before using. * @return time_latest */ guint mdiary_get_time_latest(void) { return time_latest; } /** * @brief mainWindow_taglist_to_string concatenates a list with commas * @param list is a GList of strings to concatenate * @return The string, it needs to be freed using g_free(). */ static gchar *mdiary_taglist_to_string(GList *list) { GString *str = NULL; gchar *ret = NULL; GList *l; for (l = list; l != NULL; l = l->next) { if (str == NULL) str = g_string_new(""); else str = g_string_append(str, ", "); str = g_string_append(str, l->data); } ret = str->str; g_string_free(str, 0); return ret; } /** * @brief mdiary_add_entry_to_store adds the specified entry to the store. All params are copied. * @param entryListStore target store * @param title Entry title * @param datetime Entry GDateTime * @param tags Entry tags * @param text Entry MD text (with header) */ void mdiary_add_entry_to_store(GtkListStore *entryListStore, gchar *title, GDateTime *datetime, GList *tags, gchar *summary, gchar *text) { GtkTreeIter iter; GDateTime *datetime_copy; GList *taglist_copy; gchar *date_text; gchar *taglistString; datetime_copy = g_date_time_add(datetime, 0); date_text = g_date_time_format(datetime_copy, "%A, %e %B %Y %R"); taglist_copy = g_list_copy_deep(tags, (GCopyFunc) g_strdup, NULL); taglistString = mdiary_taglist_to_string(taglist_copy); gtk_list_store_append(entryListStore, &iter); gtk_list_store_set(entryListStore, &iter, COL_TITLE, title, COL_DATE_TEXT, date_text, COL_TAGS_TEXT, taglistString, COL_TIMESTAMP, datetime_copy, COL_TAGLIST, taglist_copy, /* TODO: Verify that the duplication worked! */ COL_SUMMARY, summary, /* Automatically strdupd */ COL_TEXT, text, /* Automatically strdupd */ -1); g_free(date_text); }