}
}
+/**
+ * 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
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);
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__)
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 \
--- /dev/null
+#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);
+}
* 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) \