changeset 570:aaf518c87628

util: introduce dir module
author David Demelier <markand@malikania.fr>
date Thu, 09 Mar 2023 20:30:00 +0100
parents e13d79a14791
children cba66f7d8a53
files libmlk-util/CMakeLists.txt libmlk-util/mlk/util/dir.c libmlk-util/mlk/util/dir.h libmlk-util/mlk/util/sysconfig.cmake.h tests/CMakeLists.txt tests/assets/dir/a.txt tests/assets/dir/b.txt tests/assets/dir/c.txt tests/test-dir.c
diffstat 6 files changed, 344 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/libmlk-util/CMakeLists.txt	Thu Mar 09 13:48:08 2023 +0100
+++ b/libmlk-util/CMakeLists.txt	Thu Mar 09 20:30:00 2023 +0100
@@ -24,6 +24,8 @@
 
 set(
 	SOURCES
+	${libmlk-util_SOURCE_DIR}/mlk/util/dir.c
+	${libmlk-util_SOURCE_DIR}/mlk/util/dir.h
 	${libmlk-util_SOURCE_DIR}/mlk/util/fmemopen.c
 	${libmlk-util_SOURCE_DIR}/mlk/util/openbsd/basename.c
 	${libmlk-util_SOURCE_DIR}/mlk/util/openbsd/dirname.c
@@ -38,6 +40,7 @@
 check_symbol_exists("PATH_MAX" "limits.h" MLK_HAVE_PATH_MAX)
 
 check_include_file("libgen.h" MLK_HAVE_LIBGEN_H)
+check_include_file("dirent.h" MLK_HAVE_DIRENT_H)
 
 check_function_exists(basename MLK_HAVE_BASENAME)
 check_function_exists(dirname MLK_HAVE_DIRNAME)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-util/mlk/util/dir.c	Thu Mar 09 20:30:00 2023 +0100
@@ -0,0 +1,153 @@
+/*
+ * dir.c -- portable directory iterator
+ *
+ * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+
+#include "dir.h"
+#include "sysconfig.h"
+
+#if defined(MLK_HAVE_DIRENT_H)
+
+/* {{{ Generic dirent.h support. */
+
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct self {
+	DIR *dp;
+	struct dirent *entry;
+};
+
+static inline int
+skip(const struct self *self)
+{
+	return strcmp(self->entry->d_name, ".")  == 0 ||
+	       strcmp(self->entry->d_name, "..") == 0;
+}
+
+static void
+handle_finish(struct mlk_dir_iter *iter)
+{
+	struct self *self = iter->handle;
+
+	if (self) {
+		closedir(self->dp);
+		free(self);
+	}
+
+	iter->entry = NULL;
+	iter->handle = NULL;
+}
+
+static int
+handle_open(struct mlk_dir_iter *iter, const char *path)
+{
+	struct self *self;
+
+	if (!(self = calloc(1, sizeof (*self))))
+		return -1;
+	if (!(self->dp = opendir(path))) {
+		free(self);
+		return -1;
+	}
+
+	iter->handle = self;
+
+	return 0;
+}
+
+static int
+handle_next(struct mlk_dir_iter *iter)
+{
+	struct self *self = iter->handle;
+
+	/* Skip . and .. which are barely useful. */
+	while ((self->entry = readdir(self->dp)) && skip(self))
+		continue;
+
+	/* End of directory, free all and indicate EOF to the user. */
+	if (!self->entry) {
+		handle_finish(iter);
+		return 0;
+	}
+
+	iter->entry = self->entry->d_name;
+
+	return 1;
+}
+
+/* }}} */
+
+#else
+
+/* {{{ No-op implementation */
+
+#include <stddef.h>
+
+int
+handle_open(struct mlk_dir_iter *iter, const char *path)
+{
+	(void)path;
+
+	iter->entry = NULL;
+	iter->handle = NULL;
+
+	return -1;
+}
+
+int
+handle_next(struct mlk_dir_iter *iter)
+{
+	(void)iter;
+
+	return 0;
+}
+
+void
+handle_finish(struct mlk_dir_iter *iter)
+{
+	(void)iter;
+}
+
+/* }}} */
+
+#endif
+
+int
+mlk_dir_open(struct mlk_dir_iter *iter, const char *path)
+{
+	assert(path);
+
+	return handle_open(iter, path);
+}
+
+int
+mlk_dir_next(struct mlk_dir_iter *iter)
+{
+	assert(iter);
+
+	return handle_next(iter);
+}
+
+void
+mlk_dir_finish(struct mlk_dir_iter *iter)
+{
+	return handle_finish(iter);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-util/mlk/util/dir.h	Thu Mar 09 20:30:00 2023 +0100
@@ -0,0 +1,111 @@
+/*
+ * dir.h -- portable directory iterator
+ *
+ * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MLK_UTIL_DIR_H
+#define MLK_UTIL_DIR_H
+
+/**
+ * \file dir.h
+ * \brief Portable directory iterator
+ *
+ * This module provides a convenient portable directory iterator.
+ *
+ * How to use:
+ *
+ * 1. Use ::mlk_dir_open on your directory.
+ * 2. Call ::mlk_dir_next in a loop.
+ *
+ * The iterator is destroyed when the last iterator value has been read
+ * otherwise ::mlk_dir_finish can be used in case the loop was terminated early
+ *
+ * ```c
+ * struct mlk_dir_iter iter;
+ *
+ * if (mlk_dir_open(&iter, "/tmp") < 0) {
+ *     fprintf(stderr, "unable to open the directory\n");
+ *     return -1;
+ * }
+ *
+ * while (mlk_dir_next(&iter)) {
+ *     printf("-> %s\n", iter->entry);
+ * }
+ *
+ * // At this step the directory iterator is already destroyed for convenience.
+ * ```
+ */
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/**
+ * \struct mlk_dir_iter
+ * \brief Directory iterator structure
+ */
+struct mlk_dir_iter {
+	/**
+	 * (read-only, borrowed)
+	 *
+	 * Relative directory file entry name.
+	 */
+	const char *entry;
+
+	/** \cond MLK_PRIVATE_DECLS */
+	void *handle;
+	/** \endcond MLK_PRIVATE_DECLS */
+};
+
+/**
+ * Open the directory iterator specified by path.
+ *
+ * \pre iter != NULL
+ * \pre path != NULL
+ * \param iter the iterator to initialize
+ * \param path the path to the directory
+ * \return 0 on success or -1 on error
+ */
+int
+mlk_dir_open(struct mlk_dir_iter *iter, const char *path);
+
+/**
+ * Read the next directory entry.
+ *
+ * \pre iter != NULL
+ * \param iter the directory iterator
+ * \return 1 on read or 0 on EOF
+ */
+int
+mlk_dir_next(struct mlk_dir_iter *iter);
+
+/**
+ * Cleanup directory iterator resources.
+ *
+ * This function is only required if you didn't complete the loop using
+ * ::mlk_dir_next function.
+ *
+ * \pre iter != NULL
+ * \param iter the directory iterator
+ */
+void
+mlk_dir_finish(struct mlk_dir_iter *iter);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* !MLK_UTIL_DIR_H */
--- a/libmlk-util/mlk/util/sysconfig.cmake.h	Thu Mar 09 13:48:08 2023 +0100
+++ b/libmlk-util/mlk/util/sysconfig.cmake.h	Thu Mar 09 20:30:00 2023 +0100
@@ -21,6 +21,7 @@
 
 #cmakedefine MLK_HAVE_PATH_MAX
 
+#cmakedefine MLK_HAVE_DIRENT_H
 #cmakedefine MLK_HAVE_LIBGEN_H
 
 #cmakedefine MLK_HAVE_BASENAME
--- a/tests/CMakeLists.txt	Thu Mar 09 13:48:08 2023 +0100
+++ b/tests/CMakeLists.txt	Thu Mar 09 20:30:00 2023 +0100
@@ -24,6 +24,7 @@
 	action-script
 	alloc
 	color
+	dir
 	drawable
 	save
 	save-quest
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-dir.c	Thu Mar 09 20:30:00 2023 +0100
@@ -0,0 +1,75 @@
+/*
+ * test-dir.c -- test directory iterator
+ *
+ * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <mlk/util/dir.h>
+
+#include <dt.h>
+
+static void
+test_basics(void)
+{
+	struct mlk_dir_iter iter;
+	int a = 0, b = 0, c = 0;
+
+	DT_EQ_INT(mlk_dir_open(&iter, DIRECTORY "/dir"), 0);
+
+	/* Order is not guaranteed. */
+	while (mlk_dir_next(&iter)) {
+		if (strcmp(iter.entry, "a.txt") == 0)
+			a = 1;
+		if (strcmp(iter.entry, "b.txt") == 0)
+			b = 1;
+		if (strcmp(iter.entry, "c.txt") == 0)
+			c = 1;
+	}
+
+	DT_ASSERT(a);
+	DT_ASSERT(b);
+	DT_ASSERT(c);
+
+	/* At this point, everything should be empty. */
+	DT_EQ_PTR(iter.entry, NULL);
+	DT_EQ_PTR(iter.handle, NULL);
+}
+
+static void
+test_break(void)
+{
+	/*
+	 * This test should ensure there are no memory leaks because we break
+	 * the loop while the iterator hasn't completed.
+	 */
+	struct mlk_dir_iter iter;
+
+	DT_EQ_INT(mlk_dir_open(&iter, DIRECTORY "/dir"), 0);
+	DT_EQ_INT(mlk_dir_next(&iter), 1);
+
+	mlk_dir_finish(&iter);
+
+	/* At this point, everything should be empty. */
+	DT_EQ_PTR(iter.entry, NULL);
+	DT_EQ_PTR(iter.handle, NULL);
+}
+
+int
+main(void)
+{
+	DT_RUN(test_basics);
+	DT_RUN(test_break);
+	DT_SUMMARY();
+}