# HG changeset patch # User David Demelier # Date 1678390200 -3600 # Node ID aaf518c87628ad358944bd58d31130f72538bbae # Parent e13d79a14791faabef122ca8ffeb6273cddf5ce5 util: introduce dir module diff -r e13d79a14791 -r aaf518c87628 libmlk-util/CMakeLists.txt --- 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) diff -r e13d79a14791 -r aaf518c87628 libmlk-util/mlk/util/dir.c --- /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 + * + * 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 + +#include "dir.h" +#include "sysconfig.h" + +#if defined(MLK_HAVE_DIRENT_H) + +/* {{{ Generic dirent.h support. */ + +#include +#include +#include +#include + +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 + +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); +} diff -r e13d79a14791 -r aaf518c87628 libmlk-util/mlk/util/dir.h --- /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 + * + * 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 */ diff -r e13d79a14791 -r aaf518c87628 libmlk-util/mlk/util/sysconfig.cmake.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 diff -r e13d79a14791 -r aaf518c87628 tests/CMakeLists.txt --- 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 diff -r e13d79a14791 -r aaf518c87628 tests/assets/dir/a.txt diff -r e13d79a14791 -r aaf518c87628 tests/assets/dir/b.txt diff -r e13d79a14791 -r aaf518c87628 tests/assets/dir/c.txt diff -r e13d79a14791 -r aaf518c87628 tests/test-dir.c --- /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 + * + * 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 + +#include + +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(); +}