]> granicus.if.org Git - neomutt/commitdiff
Add more convenient functions to map file lines
authorIan Zimmerman <itz@no-use.mooo.com>
Thu, 22 Nov 2018 14:38:03 +0000 (06:38 -0800)
committerRichard Russon <rich@flatcap.org>
Mon, 26 Nov 2018 10:35:47 +0000 (10:35 +0000)
mutt/file.c
mutt/file.h
test/Makefile.autosetup
test/file.c [new file with mode: 0644]
test/main.c

index aa999fbfbd84760a9a45926cfa0a971bfa8b0cd2..b7b4865446a8d5c354990d85216175bab3f35116 100644 (file)
@@ -688,6 +688,55 @@ char *mutt_file_read_line(char *s, size_t *size, FILE *fp, int *line, int flags)
   }
 }
 
+/**
+ * mutt_file_iter_line - iterate over the lines from an open file pointer
+ * @param iter  State of iteration including ptr to line
+ * @param fp    File pointer to read from
+ * @param flags Same as mutt_file_read_line()
+ * @retval      true iff data read, false on eof
+ *
+ * This is a slightly cleaner interface for mutt_file_read_line() which avoids
+ * the eternal C loop initialization ugliness.  Use like this:
+ *
+ * ```
+ * struct MuttFileIter iter = { 0 };
+ * while (mutt_file_iter_line(&iter, fp, flags))
+ * {
+ *   do_stuff(iter.line, iter.line_num);
+ * }
+ * ```
+ */
+bool mutt_file_iter_line(struct MuttFileIter *iter, FILE *fp, int flags)
+{
+  char *p = mutt_file_read_line(iter->line, &iter->size, fp, &iter->line_num, flags);
+  if (!p)
+    return false;
+  iter->line = p;
+  return true;
+}
+
+/**
+ * mutt_file_map_lines - Process lines of text read from a file pointer
+ * @param func      Callback function to call for each line, see mutt_file_map_t
+ * @param user_data Arbitrary data passed to ``func''
+ * @param fp        File pointer to read from
+ * @param flags     Same as mutt_file_read_line()
+ * @retval          true iff all data mapped, false if ``func'' returns false
+ */
+bool mutt_file_map_lines(mutt_file_map_t func, void *user_data, FILE *fp, int flags)
+{
+  struct MuttFileIter iter = { 0 };
+  while (mutt_file_iter_line(&iter, fp, flags))
+  {
+    if (!(*func)(iter.line, iter.line_num, user_data))
+    {
+      FREE(&iter.line);
+      return false;
+    }
+  }
+  return true;
+}
+
 /**
  * mutt_file_quote_filename - Quote a filename to survive the shell's quoting rules
  * @param filename String to convert
index 92ac96b288e1bb6507ef09e77152f629c3126fb4..87681fa8e47a80bcd17092eff76959b5f54767e6 100644 (file)
@@ -59,6 +59,26 @@ enum MuttStatType
   MUTT_STAT_CTIME
 };
 
+/**
+ * struct MuttFileIter - State record for mutt_file_iter_line()
+ */
+struct MuttFileIter
+{
+  char *line;   /**< the line data */
+  size_t size;  /**< allocated size of line data */
+  int line_num; /**< line number */
+};
+
+/**
+ * typedef mutt_file_map_t - Callback function for mutt_file_map_lines()
+ * @param line      Line of text read
+ * @param line_num  Line number
+ * @param user_data Data to pass to the callback function
+ * @retval true  Read was successful
+ * @retval false Abort the reading and free the string
+ */
+typedef bool (*mutt_file_map_t)(char *line, int line_num, void *user_data);
+
 int         mutt_file_check_empty(const char *path);
 int         mutt_file_chmod(const char *path, mode_t mode);
 int         mutt_file_chmod_add(const char *path, mode_t mode);
@@ -75,7 +95,9 @@ FILE *      mutt_file_fopen(const char *path, const char *mode);
 int         mutt_file_fsync_close(FILE **f);
 long        mutt_file_get_size(const char *path);
 void        mutt_file_get_stat_timespec(struct timespec *dest, struct stat *sb, enum MuttStatType type);
+bool        mutt_file_iter_line(struct MuttFileIter *iter, FILE *fp, int flags);
 int         mutt_file_lock(int fd, bool excl, bool timeout);
+bool        mutt_file_map_lines(mutt_file_map_t func, void *user_data, FILE *fp, int flags);
 int         mutt_file_mkdir(const char *path, mode_t mode);
 FILE *      mutt_file_mkstemp_full(const char *file, int line, const char *func);
 #define     mutt_file_mkstemp() mutt_file_mkstemp_full(__FILE__, __LINE__, __func__)
index 47f59e40d8b04748fdeab0c2575c003749b597b2..5275446b4820580b4eb73f39cf6c69ca62502448 100644 (file)
@@ -5,7 +5,8 @@ TEST_OBJS   = test/main.o \
              test/rfc2047.o \
              test/string.o \
              test/address.o \
-             test/url.o
+             test/url.o \
+          test/file.o
 
 CONFIG_OBJS    = test/config/main.o test/config/account.o \
                  test/config/address.o test/config/bool.o \
diff --git a/test/file.c b/test/file.c
new file mode 100644 (file)
index 0000000..89525ce
--- /dev/null
@@ -0,0 +1,118 @@
+#define TEST_NO_MAIN
+#include "acutest.h"
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include "mutt/file.h"
+
+static const char *lines[] = {
+  "This is the first line.",
+  "The second line.",
+  "And the third line",
+  NULL,
+};
+
+#define NUM_TEST_LINES (sizeof(lines) / sizeof(const char *) - 1)
+#define BOOLIFY(x) ((x) ? "true" : "false")
+#define SET_UP() (set_up(__func__))
+#define TEAR_DOWN(fp) (tear_down((fp), __func__))
+
+static FILE *set_up(const char *funcname)
+{
+  int res = 0;
+  FILE *fp = tmpfile();
+  const char **linep = NULL;
+  if (!fp)
+    goto err1;
+  for (linep = lines; *linep; linep++)
+  {
+    res = fputs(*linep, fp);
+    if (res == EOF)
+      goto err2;
+    res = fputc('\n', fp);
+    if (res == EOF)
+      goto err2;
+  }
+  rewind(fp);
+  return fp;
+err2:
+  fclose(fp);
+err1:
+  TEST_MSG("Failed to set up test %s", funcname);
+  return NULL;
+}
+
+static void tear_down(FILE *fp, const char *funcname)
+{
+  int res = fclose(fp);
+  if (res == EOF)
+    TEST_MSG("Failed to tear down test %s", funcname);
+}
+
+void test_file_iter_line(void)
+{
+  FILE *fp = SET_UP();
+  if (!fp)
+    return;
+  struct MuttFileIter iter = { 0 };
+  bool res;
+  for (int i = 0; i < NUM_TEST_LINES; i++)
+  {
+    res = mutt_file_iter_line(&iter, fp, 0);
+    if (!TEST_CHECK(res))
+    {
+      TEST_MSG("Expected: true");
+      TEST_MSG("Actual: false");
+    }
+    if (!TEST_CHECK(strcmp(iter.line, lines[i]) == 0))
+    {
+      TEST_MSG("Expected: %s", lines[i]);
+      TEST_MSG("Actual: %s", iter.line);
+    }
+    if (!TEST_CHECK(iter.line_num == i + 1))
+    {
+      TEST_MSG("Expected: %d", i + 1);
+      TEST_MSG("Actual: %d", iter.line_num);
+    }
+  }
+  res = mutt_file_iter_line(&iter, fp, 0);
+  if (!TEST_CHECK(!res))
+  {
+    TEST_MSG("Expected: false");
+    TEST_MSG("Actual: true");
+  }
+  TEAR_DOWN(fp);
+}
+
+static bool mapping_func(char *line, int line_num, void *user_data)
+{
+  const int *p_last_line_num = (const int *) (user_data);
+  if (!TEST_CHECK(strcmp(line, lines[line_num - 1]) == 0))
+  {
+    TEST_MSG("Expected: %s", lines[line_num - 1]);
+    TEST_MSG("Actual: %s", line);
+  }
+  return (line_num < *p_last_line_num);
+}
+
+static void test_file_map_lines_breaking_after(int last_line, bool expected)
+{
+  FILE *fp = SET_UP();
+  if (!fp)
+    return;
+  bool res = mutt_file_map_lines(mapping_func, &last_line, fp, 0);
+  if (!TEST_CHECK(res == expected))
+  {
+    TEST_MSG("Expected: %s", BOOLIFY(expected));
+    TEST_MSG("Actual: %s", BOOLIFY(res));
+  }
+  TEAR_DOWN(fp);
+}
+
+void test_file_map_lines(void)
+{
+  test_file_map_lines_breaking_after(NUM_TEST_LINES + 1, true);
+  test_file_map_lines_breaking_after(0, false);
+  test_file_map_lines_breaking_after(1, false);
+  test_file_map_lines_breaking_after(NUM_TEST_LINES, false);
+}
index 5d40467943650c8975149c0645de600b11f32b4a..f387a172b504321a3d1dc53afae9714133868621 100644 (file)
@@ -4,6 +4,8 @@
  * Add your test cases to this list.
  *****************************************************************************/
 #define NEOMUTT_TEST_LIST                                                      \
+  NEOMUTT_TEST_ITEM(test_file_iter_line)                                       \
+  NEOMUTT_TEST_ITEM(test_file_map_lines)                                       \
   NEOMUTT_TEST_ITEM(test_base64_encode)                                        \
   NEOMUTT_TEST_ITEM(test_base64_decode)                                        \
   NEOMUTT_TEST_ITEM(test_base64_lengths)                                       \