├── .github └── workflows │ └── cmake.yml ├── .gitignore ├── COPYING ├── README.md ├── SECURITY.md ├── package.json ├── samples ├── .gitignore ├── CMakeLists.txt ├── cpp_sample.cpp ├── file_open_sample.c ├── interactive_sample.c ├── iterate_sample.c ├── list_to_file.c └── random_access_sample.c ├── tests ├── .gitignore ├── CMakeLists.txt ├── cbehave │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── apr_ring.h │ ├── cbehave.c │ ├── cbehave.h │ └── rlutil │ │ └── rlutil.h ├── file_open_test.c ├── util.c ├── util.h └── windows_unicode_test.c └── tinydir.h /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | build_type: [Release, Debug] 15 | os: [macos-latest, ubuntu-latest, windows-latest] 16 | CC: [clang, gcc, cl] 17 | gcc_version: [latest, 11] 18 | exclude: 19 | # macos+clang only 20 | - os: macos-latest 21 | CC: gcc 22 | - os: macos-latest 23 | CC: cl 24 | # cl for windows only 25 | - os: ubuntu-latest 26 | CC: cl 27 | - os: windows-latest 28 | CC: gcc 29 | - os: windows-latest 30 | CC: clang 31 | - CC: clang 32 | gcc_version: 11 33 | 34 | steps: 35 | - uses: actions/checkout@v2 36 | 37 | - name: Install GCC 38 | if: matrix.CC == 'gcc' 39 | uses: egor-tensin/setup-gcc@v1 40 | with: 41 | version: ${{ matrix.gcc_version }} 42 | 43 | - name: Build 44 | env: 45 | CC: ${{ matrix.CC }} 46 | run: | 47 | cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON tests 48 | cmake --build ${{github.workspace}}/build --config ${{matrix.build_type}} 49 | 50 | - name: Test 51 | working-directory: ${{github.workspace}}/build 52 | run: ctest -C ${{matrix.build_type}} 53 | 54 | - name: Build (samples) 55 | env: 56 | CC: ${{ matrix.CC }} 57 | run: | 58 | cmake -B ${{github.workspace}}/build_samples -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON samples 59 | cmake --build ${{github.workspace}}/build_samples --config ${{matrix.build_type}} 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2016, tinydir authors: 2 | - Cong Xu 3 | - Lautis Sun 4 | - Baudouin Feildel 5 | - Andargor 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are met: 10 | 11 | 1. Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | 2. Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TinyDir 2 | ======= 3 | [![CMake](https://github.com/cxong/tinydir/actions/workflows/cmake.yml/badge.svg)](https://github.com/cxong/tinydir/actions/workflows/cmake.yml) 4 | [![Release](http://img.shields.io/github/release/cxong/tinydir.svg)](https://github.com/cxong/tinydir/releases/latest) 5 | 6 | Lightweight, portable and easy to integrate C directory and file reader. TinyDir wraps dirent for POSIX and FindFirstFile for Windows. 7 | 8 | Windows unicode is supported by defining `UNICODE` and `_UNICODE` before including `tinydir.h`. 9 | 10 | Example 11 | ======= 12 | 13 | There are two methods. Error checking omitted: 14 | 15 | ```C 16 | tinydir_dir dir; 17 | tinydir_open(&dir, "/path/to/dir"); 18 | 19 | while (dir.has_next) 20 | { 21 | tinydir_file file; 22 | tinydir_readfile(&dir, &file); 23 | 24 | printf("%s", file.name); 25 | if (file.is_dir) 26 | { 27 | printf("/"); 28 | } 29 | printf("\n"); 30 | 31 | tinydir_next(&dir); 32 | } 33 | 34 | tinydir_close(&dir); 35 | ``` 36 | 37 | ```C 38 | tinydir_dir dir; 39 | int i; 40 | tinydir_open_sorted(&dir, "/path/to/dir"); 41 | 42 | for (i = 0; i < dir.n_files; i++) 43 | { 44 | tinydir_file file; 45 | tinydir_readfile_n(&dir, &file, i); 46 | 47 | printf("%s", file.name); 48 | if (file.is_dir) 49 | { 50 | printf("/"); 51 | } 52 | printf("\n"); 53 | } 54 | 55 | tinydir_close(&dir); 56 | ``` 57 | 58 | See the `/samples` folder for more examples, including an interactive command-line directory navigator. 59 | 60 | Language 61 | ======== 62 | 63 | ANSI C, or C90. 64 | 65 | Platforms 66 | ========= 67 | 68 | POSIX and Windows supported. Open to the possibility of supporting other platforms. 69 | 70 | License 71 | ======= 72 | 73 | Simplified BSD; if you use tinydir you can comply by including `tinydir.h` or `COPYING` somewhere in your package. 74 | 75 | Known Limitations 76 | ================= 77 | 78 | - Limited path and filename sizes 79 | - [Possible race condition bug if folder being read has changing content](https://github.com/cxong/tinydir/issues/13) 80 | - Does not support extended-length path lengths in Windows - paths are limited to 260 characters. See 81 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Only the latest version is supported. Check the releases section. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Go to the Security section and report a Security Advisory: https://github.com/cxong/tinydir/security/advisories 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tinydir", 3 | "description": "Lightweight, portable and easy to integrate C directory and file reader", 4 | "license": "BSD-2-Clause", 5 | "keywords": [ 6 | "dir", 7 | "directory", 8 | "file", 9 | "reader", 10 | "filesystem" 11 | ], 12 | "src": [ 13 | "tinydir.h" 14 | ], 15 | "version": "1.2.4", 16 | "repo": "cxong/tinydir" 17 | } 18 | -------------------------------------------------------------------------------- /samples/.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | 4 | # Libraries 5 | *.lib 6 | *.a 7 | 8 | # Shared objects (inc. Windows DLLs) 9 | *.dll 10 | *.so 11 | *.so.* 12 | *.dylib 13 | 14 | # Executables 15 | *.exe 16 | *.out 17 | *.app 18 | *_sample 19 | 20 | # CMake 21 | CMakeFiles/ 22 | CMakeCache.txt 23 | *.dir/ 24 | cmake_install.cmake 25 | 26 | # Visual Studio 27 | .vs/ 28 | Debug/ 29 | out/ 30 | Win32/ 31 | *.opensdf 32 | *.sdf 33 | *.suo 34 | *.vcxproj.user 35 | *.vcxproj 36 | *.vcxproj.filters 37 | *.VC.* 38 | *.sln 39 | 40 | # Linux 41 | Makefile 42 | 43 | # OS X 44 | CMakeScripts/ 45 | *.xcodeproj 46 | -------------------------------------------------------------------------------- /samples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(tinydir) 4 | 5 | INCLUDE_DIRECTORIES(..) 6 | 7 | ################################ 8 | # Add definitions 9 | 10 | if(MSVC) 11 | add_definitions(-W4 -WX -wd"4996") 12 | else() 13 | add_definitions(-fsigned-char -Wall -W -Wshadow -Wpointer-arith -Wcast-qual -Winline -Werror) 14 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wstrict-prototypes") 15 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") 16 | endif() 17 | 18 | ################################ 19 | # Add targets 20 | add_executable(file_open_sample file_open_sample.c) 21 | add_executable(iterate_sample iterate_sample.c) 22 | add_executable(random_access_sample random_access_sample.c) 23 | add_executable(interactive_sample interactive_sample.c) 24 | add_executable(cpp_sample cpp_sample.cpp) 25 | add_executable(list_to_file list_to_file.c) 26 | -------------------------------------------------------------------------------- /samples/cpp_sample.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct tinydir_dir; 7 | 8 | class TinyDir { 9 | public: 10 | TinyDir(const std::string& path); 11 | ~TinyDir(); 12 | std::string baseName() const; 13 | std::list listDir(); 14 | 15 | private: 16 | tinydir_dir* dir; 17 | }; 18 | 19 | #include 20 | 21 | TinyDir::TinyDir(const std::string& path) : dir(new tinydir_dir) { 22 | if (tinydir_open(dir, path.c_str()) == -1) { 23 | throw std::invalid_argument{"path"}; 24 | } 25 | } 26 | 27 | TinyDir::~TinyDir() { 28 | tinydir_close(dir); 29 | delete dir; 30 | } 31 | 32 | std::string TinyDir::baseName() const { 33 | const std::string path{dir->path}; 34 | auto lastSlash = path.find_last_of("/\\"); 35 | if (lastSlash == std::string::npos) { 36 | return path; 37 | } 38 | return path.substr(lastSlash + 1); 39 | } 40 | 41 | std::list TinyDir::listDir() 42 | { 43 | std::list files; 44 | 45 | while (dir->has_next) 46 | { 47 | tinydir_file file; 48 | tinydir_readfile(dir, &file); 49 | 50 | files.push_back(std::string{dir->path} + "/" + file.name); 51 | 52 | tinydir_next(dir); 53 | } 54 | 55 | return files; 56 | } 57 | 58 | int main(int argc, char* argv[]) { 59 | if (argc != 2) { 60 | std::cerr << "Usage: cpp_sample filename\n"; 61 | return 1; 62 | } 63 | TinyDir td{argv[1]}; 64 | std::cout << "Basename is " << td.baseName() << "\n"; 65 | return 0; 66 | } 67 | 68 | -------------------------------------------------------------------------------- /samples/file_open_sample.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | tinydir_file file; 7 | if (argc != 2) 8 | { 9 | fprintf(stderr, "Usage: test filename\n"); 10 | return 1; 11 | } 12 | if (tinydir_file_open(&file, argv[1]) == -1) 13 | { 14 | perror("Error opening file"); 15 | return 1; 16 | } 17 | printf("Path: %s\nName: %s\nExtension: %s\nIs dir? %s\nIs regular file? %s\n", 18 | file.path, file.name, file.extension, 19 | file.is_dir?"yes":"no", file.is_reg?"yes":"no"); 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /samples/interactive_sample.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) 5 | { 6 | tinydir_dir dir; 7 | if (tinydir_open_sorted(&dir, ".") == -1) 8 | { 9 | perror("Error opening file"); 10 | goto bail; 11 | } 12 | 13 | for (;;) 14 | { 15 | size_t i; 16 | char input[256]; 17 | for (i = 0; i < dir.n_files; i++) 18 | { 19 | tinydir_file file; 20 | if (tinydir_readfile_n(&dir, &file, i) == -1) 21 | { 22 | perror("Error getting file"); 23 | goto bail; 24 | } 25 | 26 | if (file.is_dir) 27 | { 28 | printf("[%u] ", (unsigned int)i); 29 | } 30 | printf("%s", file.name); 31 | if (file.is_dir) 32 | { 33 | printf("/"); 34 | } 35 | printf("\n"); 36 | } 37 | printf("?"); 38 | 39 | if (fgets(input, 256, stdin) == NULL) 40 | { 41 | break; 42 | } 43 | else 44 | { 45 | int choice = atoi(input); 46 | if (choice >= 0 && (size_t)choice < dir.n_files) 47 | { 48 | if (tinydir_open_subdir_n(&dir, choice) == -1) 49 | { 50 | perror("Error opening subdirectory"); 51 | goto bail; 52 | } 53 | } 54 | } 55 | } 56 | 57 | bail: 58 | tinydir_close(&dir); 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /samples/iterate_sample.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) 5 | { 6 | tinydir_dir dir; 7 | if (tinydir_open(&dir, ".") == -1) 8 | { 9 | perror("Error opening file"); 10 | goto bail; 11 | } 12 | 13 | while (dir.has_next) 14 | { 15 | tinydir_file file; 16 | if (tinydir_readfile(&dir, &file) == -1) 17 | { 18 | perror("Error getting file"); 19 | goto bail; 20 | } 21 | 22 | printf("%s", file.name); 23 | if (file.is_dir) 24 | { 25 | printf("/"); 26 | } 27 | printf("\n"); 28 | 29 | if (tinydir_next(&dir) == -1) 30 | { 31 | perror("Error getting next file"); 32 | goto bail; 33 | } 34 | } 35 | 36 | bail: 37 | tinydir_close(&dir); 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /samples/list_to_file.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define _UNICODE 4 | #define UNICODE 5 | #include "tinydir.h" 6 | 7 | char bom[] = "\xFF\xFE"; 8 | 9 | int main(void) { 10 | 11 | FILE *fp; 12 | tinydir_dir dir; 13 | tinydir_open(&dir, TINYDIR_STRING("/path/to/dir")); 14 | 15 | fp = 16 | #if ((defined _WIN32) && (defined _UNICODE)) 17 | _wfopen( 18 | #else 19 | fopen( 20 | #endif 21 | TINYDIR_STRING("/file/to/output"), TINYDIR_STRING("wb")); 22 | 23 | #if ((defined _WIN32) && (defined _UNICODE)) 24 | fwrite(bom, 1, 2, fp); 25 | #endif 26 | 27 | while (dir.has_next) 28 | { 29 | tinydir_file file; 30 | tinydir_readfile(&dir, &file); 31 | 32 | fwrite(file.name, sizeof(_tinydir_char_t), _tinydir_strlen(file.name), fp); 33 | if (file.is_dir) 34 | { 35 | fwrite(TINYDIR_STRING("/"), sizeof(_tinydir_char_t), _tinydir_strlen(TINYDIR_STRING("/")), fp); 36 | } 37 | fwrite(TINYDIR_STRING("\n"), sizeof(_tinydir_char_t), _tinydir_strlen(TINYDIR_STRING("/")), fp); 38 | 39 | tinydir_next(&dir); 40 | } 41 | 42 | tinydir_close(&dir); 43 | 44 | fclose(fp); 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /samples/random_access_sample.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | tinydir_dir dir; 7 | size_t i; 8 | if (tinydir_open_sorted(&dir, argc >= 2 ? argv[1] : ".") == -1) 9 | { 10 | perror("Error opening file"); 11 | goto bail; 12 | } 13 | 14 | for (i = 0; i < dir.n_files; i++) 15 | { 16 | tinydir_file file; 17 | if (tinydir_readfile_n(&dir, &file, i) == -1) 18 | { 19 | perror("Error getting file"); 20 | goto bail; 21 | } 22 | 23 | printf("%s", file.name); 24 | if (file.is_dir) 25 | { 26 | printf("/"); 27 | } 28 | printf("\n"); 29 | } 30 | 31 | bail: 32 | tinydir_close(&dir); 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | 4 | # Libraries 5 | *.lib 6 | *.a 7 | 8 | # Shared objects (inc. Windows DLLs) 9 | *.dll 10 | *.so 11 | *.so.* 12 | *.dylib 13 | 14 | # Executables 15 | *.exe 16 | *.out 17 | *.app 18 | *_sample 19 | 20 | # CMake 21 | CMakeFiles/ 22 | CMakeCache.txt 23 | *.dir/ 24 | cmake_install.cmake 25 | CTestTestfile.cmake 26 | 27 | # Visual Studio 28 | Debug/ 29 | Win32/ 30 | *.opensdf 31 | *.sdf 32 | *.suo 33 | *.vcxproj.user 34 | *.vcxproj 35 | *.vcxproj.filters 36 | *.VC.* 37 | *.sln 38 | .vs/ 39 | 40 | # Linux 41 | Makefile 42 | 43 | # OS X 44 | CMakeScripts/ 45 | *.xcodeproj 46 | build/ 47 | 48 | # Test files 49 | *.tmp 50 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(tinydir_tests C) 4 | 5 | INCLUDE_DIRECTORIES(..) 6 | include_directories(cbehave) 7 | add_subdirectory(cbehave) 8 | 9 | ################################ 10 | # Add definitions 11 | 12 | if(MSVC) 13 | add_definitions(-W4 -WX -wd"4127" -wd"4102" -wd"4996") 14 | else() 15 | add_definitions(-fsigned-char -Wall -W -Wshadow -Wpointer-arith -Wcast-qual -Winline -Werror -Wno-unused-label) 16 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wstrict-prototypes") 17 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") 18 | if("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") 19 | else() 20 | add_definitions(-Wno-pointer-to-int-cast) 21 | endif() 22 | endif() 23 | 24 | ################################ 25 | # Add tests 26 | enable_testing() 27 | 28 | add_library(util util.c util.h) 29 | 30 | add_executable(file_open_test file_open_test.c) 31 | target_link_libraries(file_open_test util cbehave) 32 | add_test(NAME file_open_test COMMAND file_open_test) 33 | 34 | if(MSVC) 35 | add_executable(windows_unicode_test windows_unicode_test.c) 36 | target_link_libraries(windows_unicode_test cbehave) 37 | add_test(NAME windows_unicode_test COMMAND windows_unicode_test) 38 | endif() 39 | -------------------------------------------------------------------------------- /tests/cbehave/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(.) 2 | 3 | if(MSVC) 4 | add_definitions(-wd"4127" -wd"4102" -wd"4996") 5 | else() 6 | if(NOT BEOS AND NOT HAIKU) 7 | add_definitions(-Wno-unused-label) 8 | endif() 9 | endif() 10 | 11 | add_library(cbehave STATIC cbehave.h cbehave.c apr_ring.h rlutil/rlutil.h) 12 | -------------------------------------------------------------------------------- /tests/cbehave/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012 Tony Bai 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /tests/cbehave/README.md: -------------------------------------------------------------------------------- 1 | cbehave - A Behavior Driven Development Framework for C 2 | ======= 3 | [![Build Status](https://travis-ci.org/cxong/cbehave.svg?branch=master)](https://travis-ci.org/cxong/cbehave) 4 | 5 | A demonstration using real C code: 6 | 7 | #include "cbehave.h" 8 | 9 | // Step 1: define your functions 10 | int add(int a, int b); 11 | 12 | // Step 2: describe behaviour and the function calls 13 | FEATURE(addition, "Addition") 14 | SCENARIO("Add two numbers") 15 | GIVEN("we have two numbers 50 and 70") 16 | int a = 50; 17 | int b = 70; 18 | WHEN("we add them together") 19 | int r = add(a, b); 20 | THEN("the result should be 120") 21 | SHOULD_INT_EQUAL(r, 120); 22 | SCENARIO_END 23 | FEATURE_END 24 | 25 | // Step 3: write empty implementations of functions 26 | int add(int a, int b) 27 | { 28 | // Step 5: write code to make the behaviour pass 29 | return a + b; 30 | } 31 | 32 | // Step 4: run tests and watch them fail (and succeed later) 33 | CBEHAVE_RUN("Calculator Features are as below:", TEST_FEATURE(addition)) 34 | 35 | Introduction 36 | ------------- 37 | CBehave - A Behavior Driven Development Framework for C. 38 | 39 | Main Features 40 | ------------- 41 | 42 | - use the "feature + scenario" structure (inspired by Cucumber) 43 | - use classical "given-when-then" template to describe behavior scenarios 44 | - support mock 45 | 46 | Example Output 47 | ------------- 48 | 49 | ******************************************************************* 50 | CBEHAVE -- A Behavior Driven Development Framework for C 51 | By Tony Bai 52 | ******************************************************************* 53 | Strstr Features are as belows: 54 | Feature: strstr 55 | Scenario: The strstr finds the first occurrence of the substring in the source string 56 | Given A source string: Lionel Messi is a great football player 57 | When we use strstr to find the first occurrence of [football] 58 | Then We should get the string: [football player] 59 | Scenario: If strstr could not find the first occurrence of the substring, it will return NULL 60 | Given A source string: FC Barcelona is a great football club. 61 | When we use strstr to find the first occurrence of [AC Milan] 62 | Then We should get no string but a NULL 63 | Summary: 64 | features: [1/1] 65 | scenarios: [2/2] 66 | 67 | Build 68 | ------ 69 | 70 | To run the examples: 71 | 72 | - Clone the project 73 | - cmake cbehave/examples 74 | 75 | To use cbehave in your CMake project: 76 | 77 | - include the cbehave directory 78 | - link against `cbehave` 79 | -------------------------------------------------------------------------------- /tests/cbehave/apr_ring.h: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | * This code draws heavily from the 4.4BSD macros 19 | * and Dean Gaudet's "splim/ring.h". 20 | * 21 | * 22 | * 23 | * We'd use Dean's code directly if we could guarantee the 24 | * availability of inline functions. 25 | */ 26 | 27 | #ifndef APR_RING_H 28 | #define APR_RING_H 29 | 30 | /** 31 | * @file apr_ring.h 32 | * @brief APR Rings 33 | */ 34 | 35 | /* 36 | * for offsetof() 37 | * 38 | * !here the apr_general.h has been removed, Tony Bai 39 | */ 40 | #include 41 | #define APR_OFFSETOF(s_type,field) offsetof(s_type,field) 42 | 43 | /** 44 | * @defgroup apr_ring Ring Macro Implementations 45 | * @ingroup APR 46 | * A ring is a kind of doubly-linked list that can be manipulated 47 | * without knowing where its head is. 48 | * @{ 49 | */ 50 | 51 | /** 52 | * The Ring Element 53 | * 54 | * A ring element struct is linked to the other elements in the ring 55 | * through its ring entry field, e.g. 56 | *
 57 |  *      struct my_element_t {
 58 |  *          APR_RING_ENTRY(my_element_t) link;
 59 |  *          int foo;
 60 |  *          char *bar;
 61 |  *      };
 62 |  * 
63 | * 64 | * An element struct may be put on more than one ring if it has more 65 | * than one APR_RING_ENTRY field. Each APR_RING_ENTRY has a corresponding 66 | * APR_RING_HEAD declaration. 67 | * 68 | * @warning For strict C standards compliance you should put the APR_RING_ENTRY 69 | * first in the element struct unless the head is always part of a larger 70 | * object with enough earlier fields to accommodate the offsetof() used 71 | * to compute the ring sentinel below. You can usually ignore this caveat. 72 | */ 73 | #define APR_RING_ENTRY(elem) \ 74 | struct { \ 75 | struct elem * volatile next; \ 76 | struct elem * volatile prev; \ 77 | } 78 | 79 | /** 80 | * The Ring Head 81 | * 82 | * Each ring is managed via its head, which is a struct declared like this: 83 | *
 84 |  *      APR_RING_HEAD(my_ring_t, my_element_t);
 85 |  *      struct my_ring_t ring, *ringp;
 86 |  * 
87 | * 88 | * This struct looks just like the element link struct so that we can 89 | * be sure that the typecasting games will work as expected. 90 | * 91 | * The first element in the ring is next after the head, and the last 92 | * element is just before the head. 93 | */ 94 | #define APR_RING_HEAD(head, elem) \ 95 | struct head { \ 96 | struct elem *next; \ 97 | struct elem *prev; \ 98 | } 99 | 100 | /** 101 | * The Ring Sentinel 102 | * 103 | * This is the magic pointer value that occurs before the first and 104 | * after the last elements in the ring, computed from the address of 105 | * the ring's head. The head itself isn't an element, but in order to 106 | * get rid of all the special cases when dealing with the ends of the 107 | * ring, we play typecasting games to make it look like one. 108 | * 109 | * Here is a diagram to illustrate the arrangements of the next and 110 | * prev pointers of each element in a single ring. Note that they point 111 | * to the start of each element, not to the APR_RING_ENTRY structure. 112 | * 113 | *
114 |  *     +->+------+<-+  +->+------+<-+  +->+------+<-+
115 |  *     |  |struct|  |  |  |struct|  |  |  |struct|  |
116 |  *    /   | elem |   \/   | elem |   \/   | elem |  \
117 |  * ...    |      |   /\   |      |   /\   |      |   ...
118 |  *        +------+  |  |  +------+  |  |  +------+
119 |  *   ...--|prev  |  |  +--|ring  |  |  +--|prev  |
120 |  *        |  next|--+     | entry|--+     |  next|--...
121 |  *        +------+        +------+        +------+
122 |  *        | etc. |        | etc. |        | etc. |
123 |  *        :      :        :      :        :      :
124 |  * 
125 | * 126 | * The APR_RING_HEAD is nothing but a bare APR_RING_ENTRY. The prev 127 | * and next pointers in the first and last elements don't actually 128 | * point to the head, they point to a phantom place called the 129 | * sentinel. Its value is such that last->next->next == first because 130 | * the offset from the sentinel to the head's next pointer is the same 131 | * as the offset from the start of an element to its next pointer. 132 | * This also works in the opposite direction. 133 | * 134 | *
135 |  *        last                            first
136 |  *     +->+------+<-+  +->sentinel<-+  +->+------+<-+
137 |  *     |  |struct|  |  |            |  |  |struct|  |
138 |  *    /   | elem |   \/              \/   | elem |  \
139 |  * ...    |      |   /\              /\   |      |   ...
140 |  *        +------+  |  |  +------+  |  |  +------+
141 |  *   ...--|prev  |  |  +--|ring  |  |  +--|prev  |
142 |  *        |  next|--+     |  head|--+     |  next|--...
143 |  *        +------+        +------+        +------+
144 |  *        | etc. |                        | etc. |
145 |  *        :      :                        :      :
146 |  * 
147 | * 148 | * Note that the offset mentioned above is different for each kind of 149 | * ring that the element may be on, and each kind of ring has a unique 150 | * name for its APR_RING_ENTRY in each element, and has its own type 151 | * for its APR_RING_HEAD. 152 | * 153 | * Note also that if the offset is non-zero (which is required if an 154 | * element has more than one APR_RING_ENTRY), the unreality of the 155 | * sentinel may have bad implications on very perverse implementations 156 | * of C -- see the warning in APR_RING_ENTRY. 157 | * 158 | * @param hp The head of the ring 159 | * @param elem The name of the element struct 160 | * @param link The name of the APR_RING_ENTRY in the element struct 161 | */ 162 | #define APR_RING_SENTINEL(hp, elem, link) \ 163 | (struct elem *)((char *)(&(hp)->next) - APR_OFFSETOF(struct elem, link)) 164 | 165 | /** 166 | * The first element of the ring 167 | * @param hp The head of the ring 168 | */ 169 | #define APR_RING_FIRST(hp) (hp)->next 170 | /** 171 | * The last element of the ring 172 | * @param hp The head of the ring 173 | */ 174 | #define APR_RING_LAST(hp) (hp)->prev 175 | /** 176 | * The next element in the ring 177 | * @param ep The current element 178 | * @param link The name of the APR_RING_ENTRY in the element struct 179 | */ 180 | #define APR_RING_NEXT(ep, link) (ep)->link.next 181 | /** 182 | * The previous element in the ring 183 | * @param ep The current element 184 | * @param link The name of the APR_RING_ENTRY in the element struct 185 | */ 186 | #define APR_RING_PREV(ep, link) (ep)->link.prev 187 | 188 | 189 | /** 190 | * Initialize a ring 191 | * @param hp The head of the ring 192 | * @param elem The name of the element struct 193 | * @param link The name of the APR_RING_ENTRY in the element struct 194 | */ 195 | #define APR_RING_INIT(hp, elem, link) do { \ 196 | APR_RING_FIRST((hp)) = APR_RING_SENTINEL((hp), elem, link); \ 197 | APR_RING_LAST((hp)) = APR_RING_SENTINEL((hp), elem, link); \ 198 | } while (0) 199 | 200 | /** 201 | * Determine if a ring is empty 202 | * @param hp The head of the ring 203 | * @param elem The name of the element struct 204 | * @param link The name of the APR_RING_ENTRY in the element struct 205 | * @return true or false 206 | */ 207 | #define APR_RING_EMPTY(hp, elem, link) \ 208 | (APR_RING_FIRST((hp)) == APR_RING_SENTINEL((hp), elem, link)) 209 | 210 | /** 211 | * Initialize a singleton element 212 | * @param ep The element 213 | * @param link The name of the APR_RING_ENTRY in the element struct 214 | */ 215 | #define APR_RING_ELEM_INIT(ep, link) do { \ 216 | APR_RING_NEXT((ep), link) = (ep); \ 217 | APR_RING_PREV((ep), link) = (ep); \ 218 | } while (0) 219 | 220 | 221 | /** 222 | * Splice the sequence ep1..epN into the ring before element lep 223 | * (..lep.. becomes ..ep1..epN..lep..) 224 | * @warning This doesn't work for splicing before the first element or on 225 | * empty rings... see APR_RING_SPLICE_HEAD for one that does 226 | * @param lep Element in the ring to splice before 227 | * @param ep1 First element in the sequence to splice in 228 | * @param epN Last element in the sequence to splice in 229 | * @param link The name of the APR_RING_ENTRY in the element struct 230 | */ 231 | #define APR_RING_SPLICE_BEFORE(lep, ep1, epN, link) do { \ 232 | APR_RING_NEXT((epN), link) = (lep); \ 233 | APR_RING_PREV((ep1), link) = APR_RING_PREV((lep), link); \ 234 | APR_RING_NEXT(APR_RING_PREV((lep), link), link) = (ep1); \ 235 | APR_RING_PREV((lep), link) = (epN); \ 236 | } while (0) 237 | 238 | /** 239 | * Splice the sequence ep1..epN into the ring after element lep 240 | * (..lep.. becomes ..lep..ep1..epN..) 241 | * @warning This doesn't work for splicing after the last element or on 242 | * empty rings... see APR_RING_SPLICE_TAIL for one that does 243 | * @param lep Element in the ring to splice after 244 | * @param ep1 First element in the sequence to splice in 245 | * @param epN Last element in the sequence to splice in 246 | * @param link The name of the APR_RING_ENTRY in the element struct 247 | */ 248 | #define APR_RING_SPLICE_AFTER(lep, ep1, epN, link) do { \ 249 | APR_RING_PREV((ep1), link) = (lep); \ 250 | APR_RING_NEXT((epN), link) = APR_RING_NEXT((lep), link); \ 251 | APR_RING_PREV(APR_RING_NEXT((lep), link), link) = (epN); \ 252 | APR_RING_NEXT((lep), link) = (ep1); \ 253 | } while (0) 254 | 255 | /** 256 | * Insert the element nep into the ring before element lep 257 | * (..lep.. becomes ..nep..lep..) 258 | * @warning This doesn't work for inserting before the first element or on 259 | * empty rings... see APR_RING_INSERT_HEAD for one that does 260 | * @param lep Element in the ring to insert before 261 | * @param nep Element to insert 262 | * @param link The name of the APR_RING_ENTRY in the element struct 263 | */ 264 | #define APR_RING_INSERT_BEFORE(lep, nep, link) \ 265 | APR_RING_SPLICE_BEFORE((lep), (nep), (nep), link) 266 | 267 | /** 268 | * Insert the element nep into the ring after element lep 269 | * (..lep.. becomes ..lep..nep..) 270 | * @warning This doesn't work for inserting after the last element or on 271 | * empty rings... see APR_RING_INSERT_TAIL for one that does 272 | * @param lep Element in the ring to insert after 273 | * @param nep Element to insert 274 | * @param link The name of the APR_RING_ENTRY in the element struct 275 | */ 276 | #define APR_RING_INSERT_AFTER(lep, nep, link) \ 277 | APR_RING_SPLICE_AFTER((lep), (nep), (nep), link) 278 | 279 | 280 | /** 281 | * Splice the sequence ep1..epN into the ring before the first element 282 | * (..hp.. becomes ..hp..ep1..epN..) 283 | * @param hp Head of the ring 284 | * @param ep1 First element in the sequence to splice in 285 | * @param epN Last element in the sequence to splice in 286 | * @param elem The name of the element struct 287 | * @param link The name of the APR_RING_ENTRY in the element struct 288 | */ 289 | #define APR_RING_SPLICE_HEAD(hp, ep1, epN, elem, link) \ 290 | APR_RING_SPLICE_AFTER(APR_RING_SENTINEL((hp), elem, link), \ 291 | (ep1), (epN), link) 292 | 293 | /** 294 | * Splice the sequence ep1..epN into the ring after the last element 295 | * (..hp.. becomes ..ep1..epN..hp..) 296 | * @param hp Head of the ring 297 | * @param ep1 First element in the sequence to splice in 298 | * @param epN Last element in the sequence to splice in 299 | * @param elem The name of the element struct 300 | * @param link The name of the APR_RING_ENTRY in the element struct 301 | */ 302 | #define APR_RING_SPLICE_TAIL(hp, ep1, epN, elem, link) \ 303 | APR_RING_SPLICE_BEFORE(APR_RING_SENTINEL((hp), elem, link), \ 304 | (ep1), (epN), link) 305 | 306 | /** 307 | * Insert the element nep into the ring before the first element 308 | * (..hp.. becomes ..hp..nep..) 309 | * @param hp Head of the ring 310 | * @param nep Element to insert 311 | * @param elem The name of the element struct 312 | * @param link The name of the APR_RING_ENTRY in the element struct 313 | */ 314 | #define APR_RING_INSERT_HEAD(hp, nep, elem, link) \ 315 | APR_RING_SPLICE_HEAD((hp), (nep), (nep), elem, link) 316 | 317 | /** 318 | * Insert the element nep into the ring after the last element 319 | * (..hp.. becomes ..nep..hp..) 320 | * @param hp Head of the ring 321 | * @param nep Element to insert 322 | * @param elem The name of the element struct 323 | * @param link The name of the APR_RING_ENTRY in the element struct 324 | */ 325 | #define APR_RING_INSERT_TAIL(hp, nep, elem, link) \ 326 | APR_RING_SPLICE_TAIL((hp), (nep), (nep), elem, link) 327 | 328 | /** 329 | * Concatenate ring h2 onto the end of ring h1, leaving h2 empty. 330 | * @param h1 Head of the ring to concatenate onto 331 | * @param h2 Head of the ring to concatenate 332 | * @param elem The name of the element struct 333 | * @param link The name of the APR_RING_ENTRY in the element struct 334 | */ 335 | #define APR_RING_CONCAT(h1, h2, elem, link) do { \ 336 | if (!APR_RING_EMPTY((h2), elem, link)) { \ 337 | APR_RING_SPLICE_BEFORE(APR_RING_SENTINEL((h1), elem, link), \ 338 | APR_RING_FIRST((h2)), \ 339 | APR_RING_LAST((h2)), link); \ 340 | APR_RING_INIT((h2), elem, link); \ 341 | } \ 342 | } while (0) 343 | 344 | /** 345 | * Prepend ring h2 onto the beginning of ring h1, leaving h2 empty. 346 | * @param h1 Head of the ring to prepend onto 347 | * @param h2 Head of the ring to prepend 348 | * @param elem The name of the element struct 349 | * @param link The name of the APR_RING_ENTRY in the element struct 350 | */ 351 | #define APR_RING_PREPEND(h1, h2, elem, link) do { \ 352 | if (!APR_RING_EMPTY((h2), elem, link)) { \ 353 | APR_RING_SPLICE_AFTER(APR_RING_SENTINEL((h1), elem, link), \ 354 | APR_RING_FIRST((h2)), \ 355 | APR_RING_LAST((h2)), link); \ 356 | APR_RING_INIT((h2), elem, link); \ 357 | } \ 358 | } while (0) 359 | 360 | /** 361 | * Unsplice a sequence of elements from a ring 362 | * @warning The unspliced sequence is left with dangling pointers at either end 363 | * @param ep1 First element in the sequence to unsplice 364 | * @param epN Last element in the sequence to unsplice 365 | * @param link The name of the APR_RING_ENTRY in the element struct 366 | */ 367 | #define APR_RING_UNSPLICE(ep1, epN, link) do { \ 368 | APR_RING_NEXT(APR_RING_PREV((ep1), link), link) = \ 369 | APR_RING_NEXT((epN), link); \ 370 | APR_RING_PREV(APR_RING_NEXT((epN), link), link) = \ 371 | APR_RING_PREV((ep1), link); \ 372 | } while (0) 373 | 374 | /** 375 | * Remove a single element from a ring 376 | * @warning The unspliced element is left with dangling pointers at either end 377 | * @param ep Element to remove 378 | * @param link The name of the APR_RING_ENTRY in the element struct 379 | */ 380 | #define APR_RING_REMOVE(ep, link) \ 381 | APR_RING_UNSPLICE((ep), (ep), link) 382 | 383 | /** 384 | * Iterate over a ring 385 | * @param ep The current element 386 | * @param head The head of the ring 387 | * @param elem The name of the element struct 388 | * @param link The name of the APR_RING_ENTRY in the element struct 389 | */ 390 | #define APR_RING_FOREACH(ep, head, elem, link) \ 391 | for (ep = APR_RING_FIRST(head); \ 392 | ep != APR_RING_SENTINEL(head, elem, link); \ 393 | ep = APR_RING_NEXT(ep, link)) 394 | 395 | /** 396 | * Iterate over a ring safe against removal of the current element 397 | * @param ep1 The current element 398 | * @param ep2 Iteration cursor 399 | * @param head The head of the ring 400 | * @param elem The name of the element struct 401 | * @param link The name of the APR_RING_ENTRY in the element struct 402 | */ 403 | #define APR_RING_FOREACH_SAFE(ep1, ep2, head, elem, link) \ 404 | for (ep1 = APR_RING_FIRST(head), ep2 = APR_RING_NEXT(ep1, link); \ 405 | ep1 != APR_RING_SENTINEL(head, elem, link); \ 406 | ep1 = ep2, ep2 = APR_RING_NEXT(ep1, link)) 407 | 408 | /* Debugging tools: */ 409 | 410 | #ifdef APR_RING_DEBUG 411 | #include 412 | #include 413 | 414 | #define APR_RING_CHECK_ONE(msg, ptr) \ 415 | fprintf(stderr, "*** %s %p\n", msg, ptr) 416 | 417 | #define APR_RING_CHECK(hp, elem, link, msg) \ 418 | APR_RING_CHECK_ELEM(APR_RING_SENTINEL(hp, elem, link), elem, link, msg) 419 | 420 | #define APR_RING_CHECK_ELEM(ep, elem, link, msg) do { \ 421 | struct elem *start = (ep); \ 422 | struct elem *here = start; \ 423 | fprintf(stderr, "*** ring check start -- %s\n", msg); \ 424 | do { \ 425 | fprintf(stderr, "\telem %p\n", here); \ 426 | fprintf(stderr, "\telem->next %p\n", \ 427 | APR_RING_NEXT(here, link)); \ 428 | fprintf(stderr, "\telem->prev %p\n", \ 429 | APR_RING_PREV(here, link)); \ 430 | fprintf(stderr, "\telem->next->prev %p\n", \ 431 | APR_RING_PREV(APR_RING_NEXT(here, link), link)); \ 432 | fprintf(stderr, "\telem->prev->next %p\n", \ 433 | APR_RING_NEXT(APR_RING_PREV(here, link), link)); \ 434 | if (APR_RING_PREV(APR_RING_NEXT(here, link), link) != here) { \ 435 | fprintf(stderr, "\t*** elem->next->prev != elem\n"); \ 436 | break; \ 437 | } \ 438 | if (APR_RING_NEXT(APR_RING_PREV(here, link), link) != here) { \ 439 | fprintf(stderr, "\t*** elem->prev->next != elem\n"); \ 440 | break; \ 441 | } \ 442 | here = APR_RING_NEXT(here, link); \ 443 | } while (here != start); \ 444 | fprintf(stderr, "*** ring check end\n"); \ 445 | } while (0) 446 | 447 | #define APR_RING_CHECK_CONSISTENCY(hp, elem, link) \ 448 | APR_RING_CHECK_ELEM_CONSISTENCY(APR_RING_SENTINEL(hp, elem, link),\ 449 | elem, link) 450 | 451 | #define APR_RING_CHECK_ELEM_CONSISTENCY(ep, elem, link) do { \ 452 | struct elem *start = (ep); \ 453 | struct elem *here = start; \ 454 | do { \ 455 | assert(APR_RING_PREV(APR_RING_NEXT(here, link), link) == here); \ 456 | assert(APR_RING_NEXT(APR_RING_PREV(here, link), link) == here); \ 457 | here = APR_RING_NEXT(here, link); \ 458 | } while (here != start); \ 459 | } while (0) 460 | 461 | #else 462 | /** 463 | * Print a single pointer value to STDERR 464 | * (This is a no-op unless APR_RING_DEBUG is defined.) 465 | * @param msg Descriptive message 466 | * @param ptr Pointer value to print 467 | */ 468 | #define APR_RING_CHECK_ONE(msg, ptr) 469 | /** 470 | * Dump all ring pointers to STDERR, starting with the head and looping all 471 | * the way around the ring back to the head. Aborts if an inconsistency 472 | * is found. 473 | * (This is a no-op unless APR_RING_DEBUG is defined.) 474 | * @param hp Head of the ring 475 | * @param elem The name of the element struct 476 | * @param link The name of the APR_RING_ENTRY in the element struct 477 | * @param msg Descriptive message 478 | */ 479 | #define APR_RING_CHECK(hp, elem, link, msg) 480 | /** 481 | * Loops around a ring and checks all the pointers for consistency. Pops 482 | * an assertion if any inconsistency is found. Same idea as APR_RING_CHECK() 483 | * except that it's silent if all is well. 484 | * (This is a no-op unless APR_RING_DEBUG is defined.) 485 | * @param hp Head of the ring 486 | * @param elem The name of the element struct 487 | * @param link The name of the APR_RING_ENTRY in the element struct 488 | */ 489 | #define APR_RING_CHECK_CONSISTENCY(hp, elem, link) 490 | /** 491 | * Dump all ring pointers to STDERR, starting with the given element and 492 | * looping all the way around the ring back to that element. Aborts if 493 | * an inconsistency is found. 494 | * (This is a no-op unless APR_RING_DEBUG is defined.) 495 | * @param ep The element 496 | * @param elem The name of the element struct 497 | * @param link The name of the APR_RING_ENTRY in the element struct 498 | * @param msg Descriptive message 499 | */ 500 | #define APR_RING_CHECK_ELEM(ep, elem, link, msg) 501 | /** 502 | * Loops around a ring, starting with the given element, and checks all 503 | * the pointers for consistency. Pops an assertion if any inconsistency 504 | * is found. Same idea as APR_RING_CHECK_ELEM() except that it's silent 505 | * if all is well. 506 | * (This is a no-op unless APR_RING_DEBUG is defined.) 507 | * @param ep The element 508 | * @param elem The name of the element struct 509 | * @param link The name of the APR_RING_ENTRY in the element struct 510 | */ 511 | #define APR_RING_CHECK_ELEM_CONSISTENCY(ep, elem, link) 512 | #endif 513 | 514 | /** @} */ 515 | 516 | #endif /* !APR_RING_H */ 517 | -------------------------------------------------------------------------------- /tests/cbehave/cbehave.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Tony Bai 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #include "cbehave.h" 25 | 26 | 27 | cbehave_scope_e cbehave_scope; 28 | 29 | static cbehave_symbol_head_t _symbol_list; 30 | 31 | static cbehave_symbol_t* lookup_symbol(const char *symbol_name, int obj_type); 32 | static void add_value(cbehave_symbol_t *s, void *value, int count); 33 | static cbehave_value_t* get_value(cbehave_symbol_t *s); 34 | 35 | #ifdef __APPLE__ 36 | #define DEFAULT_COLOR BLACK 37 | #else 38 | #define DEFAULT_COLOR GREY 39 | #endif 40 | 41 | void should_int_equal(int actual, int expected, 42 | void *state, const char *file, 43 | int line) { 44 | int *_scenario_state = (int*)state; 45 | if ((expected) != (actual)) { 46 | (*_scenario_state) = 1; 47 | setColor(RED); 48 | printf("\t\t\t%s:%d: Failed: expected[%d], but actual[%d].\n", 49 | file, 50 | line, 51 | expected, 52 | actual); 53 | setColor(DEFAULT_COLOR); 54 | } 55 | } 56 | 57 | void should_int_gt(int val1, int val2, 58 | void *state, 59 | const char *file, int line) { 60 | int *_scenario_state = (int*)state; 61 | if ((val1) <= (val2)) { 62 | (*_scenario_state) = 1; 63 | setColor(RED); 64 | printf("\t\t\t%s:%d: Failed: [%d] not greater than [%d].\n", 65 | file, 66 | line, 67 | val1, 68 | val2); 69 | setColor(DEFAULT_COLOR); 70 | } 71 | } 72 | 73 | void should_int_lt(int val1, int val2, 74 | void *state, 75 | const char *file, int line) { 76 | int *_scenario_state = (int*)state; 77 | if ((val1) >= (val2)) { 78 | (*_scenario_state) = 1; 79 | setColor(RED); 80 | printf("\t\t\t%s:%d: Failed: [%d] not less than [%d].\n", 81 | file, 82 | line, 83 | val1, 84 | val2); 85 | setColor(DEFAULT_COLOR); 86 | } 87 | } 88 | 89 | void should_int_ge(int val1, int val2, 90 | void *state, 91 | const char *file, int line) { 92 | int *_scenario_state = (int*)state; 93 | if ((val1) < (val2)) { 94 | (*_scenario_state) = 1; 95 | setColor(RED); 96 | printf("\t\t\t%s:%d: Failed: [%d] not greater than or equal to [%d].\n", 97 | file, 98 | line, 99 | val1, 100 | val2); 101 | setColor(DEFAULT_COLOR); 102 | } 103 | } 104 | 105 | void should_int_le(int val1, int val2, 106 | void *state, 107 | const char *file, int line) { 108 | int *_scenario_state = (int*)state; 109 | if ((val1) > (val2)) { 110 | (*_scenario_state) = 1; 111 | setColor(RED); 112 | printf("\t\t\t%s:%d: Failed: [%d] not less than or equal to [%d].\n", 113 | file, 114 | line, 115 | val1, 116 | val2); 117 | setColor(DEFAULT_COLOR); 118 | } 119 | } 120 | 121 | void should_str_equal(const char *actual, const char *expected, void *state, 122 | const char *file, int line) { 123 | 124 | int *_scenario_state = (int*)state; 125 | /* 126 | * both pointers are NULL or pointing to the same memory 127 | */ 128 | if (expected == actual) return; 129 | 130 | if (expected && actual) { 131 | if (!strcmp(expected, actual)) { 132 | return; 133 | } 134 | } 135 | 136 | (*_scenario_state) = 1; 137 | setColor(RED); 138 | printf("\t\t\t%s:%d: Failed: expected[%s], but actual[%s].\n", 139 | file, line, 140 | expected ? expected : "NULL", 141 | actual ? actual : "NULL"); 142 | setColor(DEFAULT_COLOR); 143 | } 144 | 145 | void should_mem_equal(const void *actual, const void *expected, size_t size, void *state, 146 | const char *file, int line) { 147 | 148 | int *_scenario_state = (int*)state; 149 | /* 150 | * both pointers are NULL or pointing to the same memory 151 | */ 152 | if (expected == actual) return; 153 | 154 | if (expected && actual) { 155 | if (!memcmp(expected, actual, size)) { 156 | return; 157 | } 158 | } 159 | 160 | (*_scenario_state) = 1; 161 | setColor(RED); 162 | printf("\t\t\t%s:%d: Failed: memory does not equal.\n", file, line); 163 | setColor(DEFAULT_COLOR); 164 | } 165 | 166 | void should_be_bool(bool actual, bool expected, void *state, const char *file, int line) { 167 | int *_scenario_state = (int*)state; 168 | if (actual != expected) { 169 | (*_scenario_state) = 1; 170 | setColor(RED); 171 | printf("\t\t\t%s:%d: Failed: actual[%d] is not a %s value.\n", 172 | file, 173 | line, 174 | actual, 175 | expected ? "true" : "false"); 176 | setColor(DEFAULT_COLOR); 177 | } 178 | } 179 | 180 | void cbehave_given_entry(const char *prompt, const char *str, void *state) { 181 | (void)(state); 182 | printf("\t\t%s %s\n", prompt, str); 183 | } 184 | 185 | void cbehave_when_entry(const char *prompt, const char *str, void *state) { 186 | (void)(state); 187 | printf("\t\t%s %s\n", prompt, str); 188 | } 189 | 190 | void cbehave_then_entry(const char *prompt, const char *str, void *state) { 191 | (void)(state); 192 | printf("\t\t%s %s\n", prompt, str); 193 | } 194 | 195 | void cbehave_scenario_entry(const char *str, void *state) { 196 | cbehave_state *cur = (cbehave_state*)state; 197 | cur->total_scenarios++; 198 | 199 | printf("\tScenario: %s\n", str); 200 | } 201 | 202 | void cbehave_feature_entry(const char *str, void *old_state, void *state) { 203 | cbehave_state *cur = (cbehave_state*)state; 204 | cbehave_state *old = (cbehave_state*)old_state; 205 | 206 | cur->total_features++; 207 | memcpy(old, state, sizeof(*cur)); 208 | 209 | printf("\nFeature: %s\n", str); 210 | } 211 | 212 | void cbehave_given_exit(void *state) { 213 | (void)(state); 214 | } 215 | 216 | void cbehave_when_exit(void *state) { 217 | (void)(state); 218 | } 219 | 220 | void cbehave_then_exit(void *state) { 221 | (void)(state); 222 | } 223 | 224 | void cbehave_scenario_exit(void *scenario_state, void *state) { 225 | int *_scenario_state = (int*)scenario_state; 226 | cbehave_state *cur = (cbehave_state*)state; 227 | 228 | if ((*_scenario_state) == 1) { 229 | cur->failed_scenarios++; 230 | } 231 | } 232 | 233 | void cbehave_feature_exit(void *old_state, void *state) { 234 | cbehave_state *cur = (cbehave_state*)state; 235 | cbehave_state *old = (cbehave_state*)old_state; 236 | 237 | if (cur->failed_scenarios > old->failed_scenarios) { 238 | cur->failed_features++; 239 | } 240 | } 241 | 242 | void cbehave_feature_return(const char *file, int line, int ret, void *state) { 243 | cbehave_state *cur = (cbehave_state*)state; 244 | 245 | if (ret == 0) 246 | { 247 | setColor(YELLOW); 248 | printf("\t\t\t%s:%d: Skipping feature due to failed assertion.\n", 249 | file, 250 | line); 251 | } 252 | else 253 | { 254 | cur->failed_scenarios++; 255 | 256 | setColor(RED); 257 | printf("\t\t\t%s:%d: Exception occurred, error code: %d.\n", 258 | file, 259 | line, 260 | ret); 261 | } 262 | setColor(DEFAULT_COLOR); 263 | } 264 | 265 | 266 | int _cbehave_runner(const char *description, const cbehave_feature *features, int count) { 267 | cbehave_state *state = NULL; 268 | int i; 269 | int ret; 270 | 271 | printf("%s\n", CBEHAVE_LOGO); 272 | printf("%s\n", description); 273 | 274 | state = (cbehave_state*)malloc(sizeof(*state)); 275 | if (!state) { 276 | setColor(RED); 277 | printf("\t%s:%d: Failed to alloc memory, error code: %d.\n", 278 | __FILE__, __LINE__, errno); 279 | setColor(DEFAULT_COLOR); 280 | return -1; 281 | } 282 | memset(state, 0, sizeof(*state)); 283 | 284 | APR_RING_INIT(&_symbol_list, cbehave_symbol_t, link); 285 | 286 | for (i = 0; i < count; i++) { 287 | features[i].func(state); 288 | } 289 | 290 | printf("\nSummary: \n"); 291 | if (state->failed_features) { 292 | setColor(RED); 293 | } else { 294 | setColor(GREEN); 295 | } 296 | printf("\tfeatures: [%d/%d]\n", 297 | state->total_features - state->failed_features, state->total_features); 298 | setColor(DEFAULT_COLOR); 299 | 300 | if (state->failed_scenarios) { 301 | setColor(RED); 302 | } else { 303 | setColor(GREEN); 304 | } 305 | printf("\tscenarios: [%d/%d]\n", state->total_scenarios - state->failed_scenarios, state->total_scenarios); 306 | setColor(DEFAULT_COLOR); 307 | 308 | ret = (state->failed_features == 0) ? 0 : 1; 309 | 310 | if (state) { 311 | free(state); 312 | } 313 | return ret; 314 | } 315 | 316 | void* cbehave_mock_obj(const char *fcname, 317 | int lineno, 318 | const char *fname, 319 | int obj_type) { 320 | cbehave_symbol_t *s = NULL; 321 | cbehave_value_t *v = NULL; 322 | void *p; 323 | 324 | s = lookup_symbol(fcname, obj_type); 325 | if (!s) { 326 | printf("\t[CBEHAVE]: can't find the symbol: <%s> which is being mocked!, %d line in file %s\n", 327 | fcname, lineno, fname); 328 | exit(EXIT_FAILURE); 329 | } 330 | 331 | if (s->always_return_flag) return s->value; 332 | 333 | v = get_value(s); 334 | if (!v) { 335 | printf("\t[CBEHAVE]: you have not set the value of mock obj <%s>!, %d line in file %s\n", 336 | fcname, lineno, fname); 337 | exit(EXIT_FAILURE); 338 | } 339 | 340 | p = v->value; 341 | 342 | APR_RING_REMOVE(v, link); 343 | free(v); 344 | 345 | return p; 346 | } 347 | 348 | 349 | void cbehave_mock_obj_return(const char *symbol_name, 350 | void *value, 351 | const char *fcname, 352 | int lineno, 353 | const char *fname, 354 | int obj_type, 355 | int count) { 356 | 357 | cbehave_symbol_t *s = lookup_symbol(symbol_name, obj_type); 358 | (void)(fcname); 359 | (void)(lineno); 360 | (void)(fname); 361 | if (!s) { 362 | errno = 0; 363 | s = (cbehave_symbol_t*)malloc(sizeof(*s)); 364 | if (!s) { 365 | printf("\t[CBEHAVE]: malloc error!, errcode[%d]\n", errno); 366 | exit(EXIT_FAILURE); 367 | } 368 | memset(s, 0, sizeof(*s)); 369 | strcpy(s->desc, symbol_name); 370 | s->obj_type = obj_type; 371 | APR_RING_INIT(&(s->value_list), cbehave_value_t, link); 372 | APR_RING_INSERT_TAIL(&_symbol_list, s, cbehave_symbol_t, link); 373 | } 374 | 375 | add_value(s, value, count); 376 | } 377 | 378 | static cbehave_symbol_t* lookup_symbol(const char *symbol_name, int obj_type) { 379 | cbehave_symbol_t *s = NULL; 380 | 381 | APR_RING_FOREACH(s, &_symbol_list, cbehave_symbol_t, link) { 382 | if (s != NULL) { 383 | if ((s->obj_type == obj_type) 384 | && (!strcmp(s->desc, symbol_name))) { 385 | return s; 386 | } 387 | } 388 | } 389 | 390 | return NULL; 391 | } 392 | 393 | static void add_value(cbehave_symbol_t *s, void *value, int count) { 394 | cbehave_value_t *v = NULL; 395 | int i; 396 | 397 | /* 398 | * make the obj always to return one same value 399 | * until another cbehave_mock_obj_return invoking 400 | */ 401 | if (count == -1) { 402 | s->always_return_flag = 1; 403 | s->value = value; 404 | return; 405 | } 406 | 407 | s->always_return_flag = 0; 408 | 409 | for (i = 0; i < count; i++) { 410 | errno = 0; 411 | v = (cbehave_value_t*)malloc(sizeof(*v)); 412 | if (!v) { 413 | printf("\t[CBEHAVE]: malloc error!, errcode[%d]\n", errno); 414 | exit(EXIT_FAILURE); 415 | } 416 | memset(v, 0, sizeof(*v)); 417 | v->value = value; 418 | 419 | APR_RING_INSERT_TAIL(&(s->value_list), v, cbehave_value_t, link); 420 | } 421 | } 422 | 423 | static cbehave_value_t* get_value(cbehave_symbol_t *s) { 424 | cbehave_value_t *v = NULL; 425 | 426 | v = APR_RING_FIRST(&(s->value_list)); 427 | return v; 428 | } 429 | 430 | -------------------------------------------------------------------------------- /tests/cbehave/cbehave.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Tony Bai 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | * @file cbehave.h 19 | * 20 | */ 21 | 22 | #ifndef _CBEHAVE_H 23 | #define _CBEHAVE_H 24 | 25 | #ifdef _cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | #ifndef _cplusplus 30 | #include 31 | #endif 32 | 33 | #include "apr_ring.h" 34 | 35 | #define CBEHAVE_LOGO \ 36 | "*******************************************************************\n\ 37 | \tCBEHAVE -- A Behavior Driven Development Framework for C\n\ 38 | \t\t\t By Tony Bai\n\ 39 | *******************************************************************" 40 | 41 | #define CBEHAVE_MAX_NAME_LEN 128 42 | 43 | typedef struct cbehave_state { 44 | int total_features; 45 | int failed_features; 46 | int total_scenarios; 47 | int failed_scenarios; 48 | } cbehave_state; 49 | 50 | typedef enum { 51 | CBEHAVE_SCOPE_NONE, 52 | CBEHAVE_SCOPE_GIVEN, 53 | CBEHAVE_SCOPE_WHEN, 54 | CBEHAVE_SCOPE_THEN 55 | } cbehave_scope_e; 56 | extern cbehave_scope_e cbehave_scope; 57 | 58 | #define END_SCOPE \ 59 | if (cbehave_scope == CBEHAVE_SCOPE_GIVEN) { \ 60 | cbehave_given_exit(_state); \ 61 | } else if (cbehave_scope == CBEHAVE_SCOPE_WHEN) { \ 62 | cbehave_when_exit(_state); \ 63 | } else if (cbehave_scope == CBEHAVE_SCOPE_THEN) { \ 64 | cbehave_then_exit(_state); \ 65 | } 66 | #define GIVEN_IMPL(x, _prompt) \ 67 | END_SCOPE \ 68 | cbehave_scope = CBEHAVE_SCOPE_GIVEN; \ 69 | cbehave_given_entry(_prompt, x, _state); 70 | #define GIVEN(x) GIVEN_IMPL(x, "Given") 71 | #define GIVEN_END 72 | 73 | #define WHEN_IMPL(x, _prompt) \ 74 | END_SCOPE \ 75 | cbehave_scope = CBEHAVE_SCOPE_WHEN; \ 76 | cbehave_when_entry(_prompt, x, _state); 77 | #define WHEN(x) WHEN_IMPL(x, "When") 78 | #define WHEN_END 79 | 80 | #define THEN_IMPL(x, _prompt) \ 81 | END_SCOPE \ 82 | cbehave_scope = CBEHAVE_SCOPE_THEN; \ 83 | cbehave_then_entry(_prompt, x, _state); 84 | #define THEN(x) THEN_IMPL(x, "Then") 85 | #define THEN_END 86 | 87 | #define AND(x) \ 88 | if (cbehave_scope == CBEHAVE_SCOPE_GIVEN) { \ 89 | GIVEN_IMPL(x, "And") \ 90 | } else if (cbehave_scope == CBEHAVE_SCOPE_WHEN) { \ 91 | WHEN_IMPL(x, "And") \ 92 | } else if (cbehave_scope == CBEHAVE_SCOPE_THEN) { \ 93 | THEN_IMPL(x, "And") \ 94 | } 95 | 96 | #define SCENARIO(x) { \ 97 | int _scenario_state = 0; \ 98 | cbehave_scenario_entry(x, _state); \ 99 | cbehave_scope = CBEHAVE_SCOPE_NONE; 100 | 101 | #define SCENARIO_END \ 102 | END_SCOPE \ 103 | cbehave_scenario_exit(&_scenario_state, _state); \ 104 | } 105 | 106 | #define FEATURE(idx, x) static void _cbehave_feature_##idx(void *_state) { \ 107 | cbehave_state _old_state; \ 108 | cbehave_feature_entry(x, &_old_state, _state); 109 | 110 | #define FEATURE_END \ 111 | _feature_over: \ 112 | cbehave_feature_exit(&_old_state, _state); \ 113 | } 114 | 115 | #define ASSERT(cond, ret) \ 116 | if (!(cond)) {\ 117 | cbehave_feature_return(__FILE__, __LINE__, ret, _state); \ 118 | goto _feature_over; \ 119 | }\ 120 | 121 | #define TEST_FEATURE(name) {_cbehave_feature_##name} 122 | 123 | #define SHOULD_INT_EQUAL(actual, expected) do { \ 124 | should_int_equal((actual), (expected), &_scenario_state, __FILE__, __LINE__); \ 125 | } while(0) 126 | 127 | #define SHOULD_INT_GT(val1, val2) do { \ 128 | should_int_gt((val1), (val2), &_scenario_state, __FILE__, __LINE__); \ 129 | } while(0) 130 | 131 | #define SHOULD_INT_LT(val1, val2) do { \ 132 | should_int_lt((val1), (val2), &_scenario_state, __FILE__, __LINE__); \ 133 | } while(0) 134 | 135 | #define SHOULD_INT_GE(val1, val2) do { \ 136 | should_int_ge((val1), (val2), &_scenario_state, __FILE__, __LINE__); \ 137 | } while(0) 138 | 139 | #define SHOULD_INT_LE(val1, val2) do { \ 140 | should_int_le((val1), (val2), &_scenario_state, __FILE__, __LINE__); \ 141 | } while(0) 142 | 143 | #define SHOULD_STR_EQUAL(actual, expected) do { \ 144 | should_str_equal((actual), (expected), &_scenario_state, __FILE__, __LINE__); \ 145 | } while(0) 146 | 147 | #define SHOULD_MEM_EQUAL(actual, expected, size) do { \ 148 | should_mem_equal((actual), (expected), (size), &_scenario_state, __FILE__, __LINE__); \ 149 | } while(0) 150 | 151 | #define SHOULD_BE_TRUE(actual) do { \ 152 | should_be_bool((actual), true, &_scenario_state, __FILE__, __LINE__); \ 153 | } while(0) 154 | 155 | #define SHOULD_BE_FALSE(actual) do { \ 156 | should_be_bool((actual), false, &_scenario_state, __FILE__, __LINE__); \ 157 | } while(0) 158 | 159 | #define CBEHAVE_RUN(_description, ...)\ 160 | int main(void) {\ 161 | cbehave_feature _cfeatures[] = {__VA_ARGS__};\ 162 | return cbehave_runner(_description, _cfeatures);\ 163 | } 164 | 165 | #define cbehave_runner(str, features) \ 166 | _cbehave_runner(str, features, sizeof(features)/sizeof(features[0])) 167 | 168 | typedef struct cbehave_feature { 169 | void (*func)(void *state); 170 | } cbehave_feature; 171 | 172 | int _cbehave_runner(const char *description, const cbehave_feature *features, int count); 173 | void should_int_equal(int actual, int expected, 174 | void *state, 175 | const char *file, int line); 176 | void should_int_gt(int val1, int val2, 177 | void *state, 178 | const char *file, int line); 179 | void should_int_lt(int val1, int val2, 180 | void *state, 181 | const char *file, int line); 182 | void should_int_ge(int val1, int val2, 183 | void *state, 184 | const char *file, int line); 185 | void should_int_le(int val1, int val2, 186 | void *state, 187 | const char *file, int line); 188 | void should_str_equal(const char *actual, const char *expected, void *state, 189 | const char *file, int line); 190 | void should_mem_equal(const void *actual, const void *expected, size_t size, void *state, 191 | const char *file, int line); 192 | void should_be_bool(bool actual, bool expected, void *state, const char *file, int line); 193 | 194 | void cbehave_given_entry(const char *prompt, const char *str, void *state); 195 | void cbehave_when_entry(const char *prompt, const char *str, void *state); 196 | void cbehave_then_entry(const char *prompt, const char *str, void *state); 197 | void cbehave_scenario_entry(const char *str, void *state); 198 | void cbehave_feature_entry(const char *str, void *old_state, void *state); 199 | 200 | void cbehave_given_exit(void *state); 201 | void cbehave_when_exit(void *state); 202 | void cbehave_then_exit(void *state); 203 | void cbehave_scenario_exit(void *scenario_state, void *state); 204 | void cbehave_feature_exit(void *old_state, void *state); 205 | void cbehave_feature_return(const char *file, int line, int ret, void *state); 206 | 207 | /* 208 | * mock symbol list 209 | * 210 | * ------------ 211 | * | symbol-#0|-> value_list 212 | * ------------ 213 | * | symbol-#1|-> value_list 214 | * ------------ 215 | * | symbol-#2|-> value_list 216 | * ------------ 217 | * | ... ... |-> value_list 218 | * ------------ 219 | * | symbol-#n|-> value_list 220 | * ------------ 221 | */ 222 | 223 | typedef struct cbehave_value_t { 224 | APR_RING_ENTRY(cbehave_value_t) link; 225 | void *value; 226 | } cbehave_value_t; 227 | typedef APR_RING_HEAD(cbehave_value_head_t, cbehave_value_t) cbehave_value_head_t; 228 | 229 | typedef struct cbehave_symbol_t { 230 | APR_RING_ENTRY(cbehave_symbol_t) link; 231 | char desc[CBEHAVE_MAX_NAME_LEN]; 232 | int obj_type; 233 | int always_return_flag; /* 1: always return the same value; 0(default) */ 234 | void* value; 235 | cbehave_value_head_t value_list; 236 | } cbehave_symbol_t; 237 | typedef APR_RING_HEAD(cbehave_symbol_head_t, cbehave_symbol_t) cbehave_symbol_head_t; 238 | 239 | void* cbehave_mock_obj(const char *fcname, int lineno, const char *fname, int obj_type); 240 | void cbehave_mock_obj_return(const char *symbol_name, void *value, const char *fcname, 241 | int lineno, const char *fname, int obj_type, int count); 242 | 243 | #define MOCK_ARG 0x0 244 | #define MOCK_RETV 0x1 245 | 246 | #define CBEHAVE_MOCK_ARG() cbehave_mock_obj(__FUNCTION__, __LINE__, __FILE__, MOCK_ARG) 247 | #define CBEHAVE_MOCK_RETV() cbehave_mock_obj(__FUNCTION__, __LINE__, __FILE__, MOCK_RETV) 248 | 249 | #define CBEHAVE_ARG_RETURN(fcname, value) do { \ 250 | cbehave_mock_obj_return(#fcname, (void*)value, __FUNCTION__, __LINE__, __FILE__, MOCK_ARG, 1); \ 251 | } while(0); 252 | 253 | #define CBEHAVE_ARG_RETURN_COUNT(fcname, value, count) do { \ 254 | cbehave_mock_obj_return(#fcname, (void*)value, __FUNCTION__, __LINE__, __FILE__, MOCK_ARG, count); \ 255 | } while(0); 256 | 257 | #define CBEHAVE_RETV_RETURN(fcname, value) do { \ 258 | cbehave_mock_obj_return(#fcname, (void*)value, __FUNCTION__, __LINE__, __FILE__, MOCK_RETV, 1); \ 259 | } while(0); 260 | 261 | #define CBEHAVE_RETV_RETURN_COUNT(fcname, value, count) do { \ 262 | cbehave_mock_obj_return(#fcname, (void*)value, __FUNCTION__, __LINE__, __FILE__, MOCK_RETV, count); \ 263 | } while(0); 264 | 265 | 266 | #ifdef _cplusplus 267 | } 268 | #endif 269 | 270 | #endif /* _CBEHAVE_H */ 271 | -------------------------------------------------------------------------------- /tests/cbehave/rlutil/rlutil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /** 3 | * File: rlutil.h 4 | * 5 | * About: Description 6 | * This file provides some useful utilities for console mode 7 | * roguelike game development with C and C++. It is aimed to 8 | * be cross-platform (at least Windows and Linux). 9 | * 10 | * About: Copyright 11 | * (C) 2010 Tapio Vierros 12 | * 13 | * About: Licensing 14 | * See 15 | */ 16 | 17 | 18 | /// Define: RLUTIL_USE_ANSI 19 | /// Define this to use ANSI escape sequences also on Windows 20 | /// (defaults to using WinAPI instead). 21 | #if 0 22 | #define RLUTIL_USE_ANSI 23 | #endif 24 | 25 | /// Define: RLUTIL_STRING_T 26 | /// Define/typedef this to your preference to override rlutil's string type. 27 | /// 28 | /// Defaults to std::string with C++ and char* with C. 29 | #if 0 30 | #define RLUTIL_STRING_T char* 31 | #endif 32 | 33 | #ifndef RLUTIL_INLINE 34 | #ifdef _MSC_VER 35 | #define RLUTIL_INLINE __inline 36 | #else 37 | #define RLUTIL_INLINE static __inline__ 38 | #endif 39 | #endif 40 | 41 | #ifdef __cplusplus 42 | /// Common C++ headers 43 | #include 44 | #include 45 | #include // for getch() 46 | /// Namespace forward declarations 47 | namespace rlutil { 48 | RLUTIL_INLINE void locate(int x, int y); 49 | } 50 | #else 51 | #include // for getch() / printf() 52 | #include // for strlen() 53 | RLUTIL_INLINE void locate(int x, int y); // Forward declare for C to avoid warnings 54 | #endif // __cplusplus 55 | 56 | #ifdef _WIN32 57 | #include // for WinAPI and Sleep() 58 | #define _NO_OLDNAMES // for MinGW compatibility 59 | #include // for getch() and kbhit() 60 | #define getch _getch 61 | #define kbhit _kbhit 62 | #else 63 | #include // for getch() and kbhit() 64 | #include // for getch(), kbhit() and (u)sleep() 65 | #include // for getkey() 66 | #include // for kbhit() 67 | #include // for kbhit() 68 | 69 | /// Function: getch 70 | /// Get character without waiting for Return to be pressed. 71 | /// Windows has this in conio.h 72 | RLUTIL_INLINE int getch(void) { 73 | // Here be magic. 74 | struct termios oldt, newt; 75 | int ch; 76 | tcgetattr(STDIN_FILENO, &oldt); 77 | newt = oldt; 78 | newt.c_lflag &= ~(ICANON | ECHO); 79 | tcsetattr(STDIN_FILENO, TCSANOW, &newt); 80 | ch = getchar(); 81 | tcsetattr(STDIN_FILENO, TCSANOW, &oldt); 82 | return ch; 83 | } 84 | 85 | /// Function: kbhit 86 | /// Determines if keyboard has been hit. 87 | /// Windows has this in conio.h 88 | RLUTIL_INLINE int kbhit(void) { 89 | // Here be dragons. 90 | static struct termios oldt, newt; 91 | int cnt = 0; 92 | tcgetattr(STDIN_FILENO, &oldt); 93 | newt = oldt; 94 | newt.c_lflag &= ~(ICANON | ECHO); 95 | newt.c_iflag = 0; // input mode 96 | newt.c_oflag = 0; // output mode 97 | newt.c_cc[VMIN] = 1; // minimum time to wait 98 | newt.c_cc[VTIME] = 1; // minimum characters to wait for 99 | tcsetattr(STDIN_FILENO, TCSANOW, &newt); 100 | ioctl(0, FIONREAD, &cnt); // Read count 101 | struct timeval tv; 102 | tv.tv_sec = 0; 103 | tv.tv_usec = 100; 104 | select(STDIN_FILENO+1, NULL, NULL, NULL, &tv); // A small time delay 105 | tcsetattr(STDIN_FILENO, TCSANOW, &oldt); 106 | return cnt; // Return number of characters 107 | } 108 | #endif // _WIN32 109 | 110 | #ifndef gotoxy 111 | /// Function: gotoxy 112 | /// Same as . 113 | RLUTIL_INLINE void gotoxy(int x, int y) { 114 | #ifdef __cplusplus 115 | rlutil:: 116 | #endif 117 | locate(x,y); 118 | } 119 | #endif // gotoxy 120 | 121 | #ifdef __cplusplus 122 | /// Namespace: rlutil 123 | /// In C++ all functions except , and are arranged 124 | /// under namespace rlutil. That is because some platforms have them defined 125 | /// outside of rlutil. 126 | namespace rlutil { 127 | #endif 128 | 129 | /** 130 | * Defs: Internal typedefs and macros 131 | * RLUTIL_STRING_T - String type depending on which one of C or C++ is used 132 | * RLUTIL_PRINT(str) - Printing macro independent of C/C++ 133 | */ 134 | 135 | #ifdef __cplusplus 136 | #ifndef RLUTIL_STRING_T 137 | typedef std::string RLUTIL_STRING_T; 138 | #endif // RLUTIL_STRING_T 139 | 140 | #define RLUTIL_PRINT(st) do { std::cout << st; } while(false) 141 | #else // __cplusplus 142 | #ifndef RLUTIL_STRING_T 143 | typedef const char* RLUTIL_STRING_T; 144 | #endif // RLUTIL_STRING_T 145 | 146 | #define RLUTIL_PRINT(st) printf("%s", st) 147 | #endif // __cplusplus 148 | 149 | /** 150 | * Enums: Color codes 151 | * 152 | * BLACK - Black 153 | * BLUE - Blue 154 | * GREEN - Green 155 | * CYAN - Cyan 156 | * RED - Red 157 | * MAGENTA - Magenta / purple 158 | * BROWN - Brown / dark yellow 159 | * GREY - Grey / dark white 160 | * DARKGREY - Dark grey / light black 161 | * LIGHTBLUE - Light blue 162 | * LIGHTGREEN - Light green 163 | * LIGHTCYAN - Light cyan 164 | * LIGHTRED - Light red 165 | * LIGHTMAGENTA - Light magenta / light purple 166 | * YELLOW - Yellow (bright) 167 | * WHITE - White (bright) 168 | */ 169 | enum { 170 | BLACK, 171 | BLUE, 172 | GREEN, 173 | CYAN, 174 | RED, 175 | MAGENTA, 176 | BROWN, 177 | GREY, 178 | DARKGREY, 179 | LIGHTBLUE, 180 | LIGHTGREEN, 181 | LIGHTCYAN, 182 | LIGHTRED, 183 | LIGHTMAGENTA, 184 | YELLOW, 185 | WHITE 186 | }; 187 | 188 | /** 189 | * Consts: ANSI escape strings 190 | * 191 | * ANSI_CLS - Clears screen 192 | * ANSI_ATTRIBUTE_RESET - Resets all attributes 193 | * ANSI_CURSOR_HIDE - Hides the cursor 194 | * ANSI_CURSOR_SHOW - Shows the cursor 195 | * ANSI_CURSOR_HOME - Moves the cursor home (0,0) 196 | * ANSI_BLACK - Black 197 | * ANSI_RED - Red 198 | * ANSI_GREEN - Green 199 | * ANSI_BROWN - Brown / dark yellow 200 | * ANSI_BLUE - Blue 201 | * ANSI_MAGENTA - Magenta / purple 202 | * ANSI_CYAN - Cyan 203 | * ANSI_GREY - Grey / dark white 204 | * ANSI_DARKGREY - Dark grey / light black 205 | * ANSI_LIGHTRED - Light red 206 | * ANSI_LIGHTGREEN - Light green 207 | * ANSI_YELLOW - Yellow (bright) 208 | * ANSI_LIGHTBLUE - Light blue 209 | * ANSI_LIGHTMAGENTA - Light magenta / light purple 210 | * ANSI_LIGHTCYAN - Light cyan 211 | * ANSI_WHITE - White (bright) 212 | * ANSI_BACKGROUND_BLACK - Black background 213 | * ANSI_BACKGROUND_RED - Red background 214 | * ANSI_BACKGROUND_GREEN - Green background 215 | * ANSI_BACKGROUND_YELLOW - Yellow background 216 | * ANSI_BACKGROUND_BLUE - Blue background 217 | * ANSI_BACKGROUND_MAGENTA - Magenta / purple background 218 | * ANSI_BACKGROUND_CYAN - Cyan background 219 | * ANSI_BACKGROUND_WHITE - White background 220 | */ 221 | const RLUTIL_STRING_T ANSI_CLS = "\033[2J\033[3J"; 222 | const RLUTIL_STRING_T ANSI_ATTRIBUTE_RESET = "\033[0m"; 223 | const RLUTIL_STRING_T ANSI_CURSOR_HIDE = "\033[?25l"; 224 | const RLUTIL_STRING_T ANSI_CURSOR_SHOW = "\033[?25h"; 225 | const RLUTIL_STRING_T ANSI_CURSOR_HOME = "\033[H"; 226 | const RLUTIL_STRING_T ANSI_BLACK = "\033[22;30m"; 227 | const RLUTIL_STRING_T ANSI_RED = "\033[22;31m"; 228 | const RLUTIL_STRING_T ANSI_GREEN = "\033[22;32m"; 229 | const RLUTIL_STRING_T ANSI_BROWN = "\033[22;33m"; 230 | const RLUTIL_STRING_T ANSI_BLUE = "\033[22;34m"; 231 | const RLUTIL_STRING_T ANSI_MAGENTA = "\033[22;35m"; 232 | const RLUTIL_STRING_T ANSI_CYAN = "\033[22;36m"; 233 | const RLUTIL_STRING_T ANSI_GREY = "\033[22;37m"; 234 | const RLUTIL_STRING_T ANSI_DARKGREY = "\033[01;30m"; 235 | const RLUTIL_STRING_T ANSI_LIGHTRED = "\033[01;31m"; 236 | const RLUTIL_STRING_T ANSI_LIGHTGREEN = "\033[01;32m"; 237 | const RLUTIL_STRING_T ANSI_YELLOW = "\033[01;33m"; 238 | const RLUTIL_STRING_T ANSI_LIGHTBLUE = "\033[01;34m"; 239 | const RLUTIL_STRING_T ANSI_LIGHTMAGENTA = "\033[01;35m"; 240 | const RLUTIL_STRING_T ANSI_LIGHTCYAN = "\033[01;36m"; 241 | const RLUTIL_STRING_T ANSI_WHITE = "\033[01;37m"; 242 | const RLUTIL_STRING_T ANSI_BACKGROUND_BLACK = "\033[40m"; 243 | const RLUTIL_STRING_T ANSI_BACKGROUND_RED = "\033[41m"; 244 | const RLUTIL_STRING_T ANSI_BACKGROUND_GREEN = "\033[42m"; 245 | const RLUTIL_STRING_T ANSI_BACKGROUND_YELLOW = "\033[43m"; 246 | const RLUTIL_STRING_T ANSI_BACKGROUND_BLUE = "\033[44m"; 247 | const RLUTIL_STRING_T ANSI_BACKGROUND_MAGENTA = "\033[45m"; 248 | const RLUTIL_STRING_T ANSI_BACKGROUND_CYAN = "\033[46m"; 249 | const RLUTIL_STRING_T ANSI_BACKGROUND_WHITE = "\033[47m"; 250 | // Remaining colors not supported as background colors 251 | 252 | /** 253 | * Enums: Key codes for keyhit() 254 | * 255 | * KEY_ESCAPE - Escape 256 | * KEY_ENTER - Enter 257 | * KEY_SPACE - Space 258 | * KEY_INSERT - Insert 259 | * KEY_HOME - Home 260 | * KEY_END - End 261 | * KEY_DELETE - Delete 262 | * KEY_PGUP - PageUp 263 | * KEY_PGDOWN - PageDown 264 | * KEY_UP - Up arrow 265 | * KEY_DOWN - Down arrow 266 | * KEY_LEFT - Left arrow 267 | * KEY_RIGHT - Right arrow 268 | * KEY_F1 - F1 269 | * KEY_F2 - F2 270 | * KEY_F3 - F3 271 | * KEY_F4 - F4 272 | * KEY_F5 - F5 273 | * KEY_F6 - F6 274 | * KEY_F7 - F7 275 | * KEY_F8 - F8 276 | * KEY_F9 - F9 277 | * KEY_F10 - F10 278 | * KEY_F11 - F11 279 | * KEY_F12 - F12 280 | * KEY_NUMDEL - Numpad del 281 | * KEY_NUMPAD0 - Numpad 0 282 | * KEY_NUMPAD1 - Numpad 1 283 | * KEY_NUMPAD2 - Numpad 2 284 | * KEY_NUMPAD3 - Numpad 3 285 | * KEY_NUMPAD4 - Numpad 4 286 | * KEY_NUMPAD5 - Numpad 5 287 | * KEY_NUMPAD6 - Numpad 6 288 | * KEY_NUMPAD7 - Numpad 7 289 | * KEY_NUMPAD8 - Numpad 8 290 | * KEY_NUMPAD9 - Numpad 9 291 | */ 292 | enum { 293 | KEY_ESCAPE = 0, 294 | KEY_ENTER = 1, 295 | KEY_SPACE = 32, 296 | 297 | KEY_INSERT = 2, 298 | KEY_HOME = 3, 299 | KEY_PGUP = 4, 300 | KEY_DELETE = 5, 301 | KEY_END = 6, 302 | KEY_PGDOWN = 7, 303 | 304 | KEY_UP = 14, 305 | KEY_DOWN = 15, 306 | KEY_LEFT = 16, 307 | KEY_RIGHT = 17, 308 | 309 | KEY_F1 = 18, 310 | KEY_F2 = 19, 311 | KEY_F3 = 20, 312 | KEY_F4 = 21, 313 | KEY_F5 = 22, 314 | KEY_F6 = 23, 315 | KEY_F7 = 24, 316 | KEY_F8 = 25, 317 | KEY_F9 = 26, 318 | KEY_F10 = 27, 319 | KEY_F11 = 28, 320 | KEY_F12 = 29, 321 | 322 | KEY_NUMDEL = 30, 323 | KEY_NUMPAD0 = 31, 324 | KEY_NUMPAD1 = 127, 325 | KEY_NUMPAD2 = 128, 326 | KEY_NUMPAD3 = 129, 327 | KEY_NUMPAD4 = 130, 328 | KEY_NUMPAD5 = 131, 329 | KEY_NUMPAD6 = 132, 330 | KEY_NUMPAD7 = 133, 331 | KEY_NUMPAD8 = 134, 332 | KEY_NUMPAD9 = 135 333 | }; 334 | 335 | /// Function: getkey 336 | /// Reads a key press (blocking) and returns a key code. 337 | /// 338 | /// See 339 | /// 340 | /// Note: 341 | /// Only Arrows, Esc, Enter and Space are currently working properly. 342 | RLUTIL_INLINE int getkey(void) { 343 | #ifndef _WIN32 344 | int cnt = kbhit(); // for ANSI escapes processing 345 | #endif 346 | int k = getch(); 347 | switch(k) { 348 | case 0: { 349 | int kk; 350 | switch (kk = getch()) { 351 | case 71: return KEY_NUMPAD7; 352 | case 72: return KEY_NUMPAD8; 353 | case 73: return KEY_NUMPAD9; 354 | case 75: return KEY_NUMPAD4; 355 | case 77: return KEY_NUMPAD6; 356 | case 79: return KEY_NUMPAD1; 357 | case 80: return KEY_NUMPAD4; 358 | case 81: return KEY_NUMPAD3; 359 | case 82: return KEY_NUMPAD0; 360 | case 83: return KEY_NUMDEL; 361 | default: return kk-59+KEY_F1; // Function keys 362 | }} 363 | case 224: { 364 | int kk; 365 | switch (kk = getch()) { 366 | case 71: return KEY_HOME; 367 | case 72: return KEY_UP; 368 | case 73: return KEY_PGUP; 369 | case 75: return KEY_LEFT; 370 | case 77: return KEY_RIGHT; 371 | case 79: return KEY_END; 372 | case 80: return KEY_DOWN; 373 | case 81: return KEY_PGDOWN; 374 | case 82: return KEY_INSERT; 375 | case 83: return KEY_DELETE; 376 | default: return kk-123+KEY_F1; // Function keys 377 | }} 378 | case 13: return KEY_ENTER; 379 | #ifdef _WIN32 380 | case 27: return KEY_ESCAPE; 381 | #else // _WIN32 382 | case 155: // single-character CSI 383 | case 27: { 384 | // Process ANSI escape sequences 385 | if (cnt >= 3 && getch() == '[') { 386 | switch (k = getch()) { 387 | case 'A': return KEY_UP; 388 | case 'B': return KEY_DOWN; 389 | case 'C': return KEY_RIGHT; 390 | case 'D': return KEY_LEFT; 391 | } 392 | } else return KEY_ESCAPE; 393 | } 394 | #endif // _WIN32 395 | default: return k; 396 | } 397 | } 398 | 399 | /// Function: nb_getch 400 | /// Non-blocking getch(). Returns 0 if no key was pressed. 401 | RLUTIL_INLINE int nb_getch(void) { 402 | if (kbhit()) return getch(); 403 | else return 0; 404 | } 405 | 406 | /// Function: getANSIColor 407 | /// Return ANSI color escape sequence for specified number 0-15. 408 | /// 409 | /// See 410 | RLUTIL_INLINE RLUTIL_STRING_T getANSIColor(const int c) { 411 | switch (c) { 412 | case BLACK : return ANSI_BLACK; 413 | case BLUE : return ANSI_BLUE; // non-ANSI 414 | case GREEN : return ANSI_GREEN; 415 | case CYAN : return ANSI_CYAN; // non-ANSI 416 | case RED : return ANSI_RED; // non-ANSI 417 | case MAGENTA : return ANSI_MAGENTA; 418 | case BROWN : return ANSI_BROWN; 419 | case GREY : return ANSI_GREY; 420 | case DARKGREY : return ANSI_DARKGREY; 421 | case LIGHTBLUE : return ANSI_LIGHTBLUE; // non-ANSI 422 | case LIGHTGREEN : return ANSI_LIGHTGREEN; 423 | case LIGHTCYAN : return ANSI_LIGHTCYAN; // non-ANSI; 424 | case LIGHTRED : return ANSI_LIGHTRED; // non-ANSI; 425 | case LIGHTMAGENTA: return ANSI_LIGHTMAGENTA; 426 | case YELLOW : return ANSI_YELLOW; // non-ANSI 427 | case WHITE : return ANSI_WHITE; 428 | default: return ""; 429 | } 430 | } 431 | 432 | /// Function: getANSIBackgroundColor 433 | /// Return ANSI background color escape sequence for specified number 0-15. 434 | /// 435 | /// See 436 | RLUTIL_INLINE RLUTIL_STRING_T getANSIBackgroundColor(const int c) { 437 | switch (c) { 438 | case BLACK : return ANSI_BACKGROUND_BLACK; 439 | case BLUE : return ANSI_BACKGROUND_BLUE; 440 | case GREEN : return ANSI_BACKGROUND_GREEN; 441 | case CYAN : return ANSI_BACKGROUND_CYAN; 442 | case RED : return ANSI_BACKGROUND_RED; 443 | case MAGENTA: return ANSI_BACKGROUND_MAGENTA; 444 | case BROWN : return ANSI_BACKGROUND_YELLOW; 445 | case GREY : return ANSI_BACKGROUND_WHITE; 446 | default: return ""; 447 | } 448 | } 449 | 450 | /// Function: setColor 451 | /// Change color specified by number (Windows / QBasic colors). 452 | /// Don't change the background color 453 | /// 454 | /// See 455 | RLUTIL_INLINE void setColor(int c) { 456 | #if defined(_WIN32) && !defined(RLUTIL_USE_ANSI) 457 | HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); 458 | CONSOLE_SCREEN_BUFFER_INFO csbi; 459 | 460 | GetConsoleScreenBufferInfo(hConsole, &csbi); 461 | 462 | SetConsoleTextAttribute(hConsole, (csbi.wAttributes & 0xFFF0) | (WORD)c); // Foreground colors take up the least significant byte 463 | #else 464 | RLUTIL_PRINT(getANSIColor(c)); 465 | #endif 466 | } 467 | 468 | /// Function: setBackgroundColor 469 | /// Change background color specified by number (Windows / QBasic colors). 470 | /// Don't change the foreground color 471 | /// 472 | /// See 473 | RLUTIL_INLINE void setBackgroundColor(int c) { 474 | #if defined(_WIN32) && !defined(RLUTIL_USE_ANSI) 475 | HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); 476 | CONSOLE_SCREEN_BUFFER_INFO csbi; 477 | 478 | GetConsoleScreenBufferInfo(hConsole, &csbi); 479 | 480 | SetConsoleTextAttribute(hConsole, (csbi.wAttributes & 0xFF0F) | (((WORD)c) << 4)); // Background colors take up the second-least significant byte 481 | #else 482 | RLUTIL_PRINT(getANSIBackgroundColor(c)); 483 | #endif 484 | } 485 | 486 | /// Function: saveDefaultColor 487 | /// Call once to preserve colors for use in resetColor() 488 | /// on Windows without ANSI, no-op otherwise 489 | /// 490 | /// See 491 | /// See 492 | RLUTIL_INLINE int saveDefaultColor() { 493 | #if defined(_WIN32) && !defined(RLUTIL_USE_ANSI) 494 | static char initialized = 0; // bool 495 | static WORD attributes; 496 | 497 | if (!initialized) { 498 | CONSOLE_SCREEN_BUFFER_INFO csbi; 499 | GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); 500 | attributes = csbi.wAttributes; 501 | initialized = 1; 502 | } 503 | return (int)attributes; 504 | #else 505 | return -1; 506 | #endif 507 | } 508 | 509 | /// Function: resetColor 510 | /// Reset color to default 511 | /// Requires a call to saveDefaultColor() to set the defaults 512 | /// 513 | /// See 514 | /// See 515 | /// See 516 | RLUTIL_INLINE void resetColor() { 517 | #if defined(_WIN32) && !defined(RLUTIL_USE_ANSI) 518 | SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (WORD)saveDefaultColor()); 519 | #else 520 | RLUTIL_PRINT(ANSI_ATTRIBUTE_RESET); 521 | #endif 522 | } 523 | 524 | /// Function: cls 525 | /// Clears screen, resets all attributes and moves cursor home. 526 | RLUTIL_INLINE void cls(void) { 527 | #if defined(_WIN32) && !defined(RLUTIL_USE_ANSI) 528 | // Based on https://msdn.microsoft.com/en-us/library/windows/desktop/ms682022%28v=vs.85%29.aspx 529 | const HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); 530 | const COORD coordScreen = {0, 0}; 531 | DWORD cCharsWritten; 532 | CONSOLE_SCREEN_BUFFER_INFO csbi; 533 | 534 | GetConsoleScreenBufferInfo(hConsole, &csbi); 535 | const DWORD dwConSize = csbi.dwSize.X * csbi.dwSize.Y; 536 | FillConsoleOutputCharacter(hConsole, (TCHAR)' ', dwConSize, coordScreen, &cCharsWritten); 537 | 538 | GetConsoleScreenBufferInfo(hConsole, &csbi); 539 | FillConsoleOutputAttribute(hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten); 540 | 541 | SetConsoleCursorPosition(hConsole, coordScreen); 542 | #else 543 | RLUTIL_PRINT(ANSI_CLS); 544 | RLUTIL_PRINT(ANSI_CURSOR_HOME); 545 | #endif 546 | } 547 | 548 | /// Function: locate 549 | /// Sets the cursor position to 1-based x,y. 550 | RLUTIL_INLINE void locate(int x, int y) { 551 | #if defined(_WIN32) && !defined(RLUTIL_USE_ANSI) 552 | COORD coord; 553 | // TODO: clamping/assert for x/y <= 0? 554 | coord.X = (SHORT)(x - 1); 555 | coord.Y = (SHORT)(y - 1); // Windows uses 0-based coordinates 556 | SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord); 557 | #else // _WIN32 || USE_ANSI 558 | #ifdef __cplusplus 559 | RLUTIL_PRINT("\033[" << y << ";" << x << "H"); 560 | #else // __cplusplus 561 | char buf[32]; 562 | sprintf(buf, "\033[%d;%df", y, x); 563 | RLUTIL_PRINT(buf); 564 | #endif // __cplusplus 565 | #endif // _WIN32 || USE_ANSI 566 | } 567 | 568 | /// Function: setString 569 | /// Prints the supplied string without advancing the cursor 570 | #ifdef __cplusplus 571 | RLUTIL_INLINE void setString(const RLUTIL_STRING_T & str_) { 572 | const char * const str = str_.data(); 573 | unsigned int len = static_cast(str_.size()); 574 | #else // __cplusplus 575 | RLUTIL_INLINE void setString(RLUTIL_STRING_T str) { 576 | unsigned int len = (unsigned int)strlen(str); 577 | #endif // __cplusplus 578 | #if defined(_WIN32) && !defined(RLUTIL_USE_ANSI) 579 | HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); 580 | DWORD numberOfCharsWritten; 581 | CONSOLE_SCREEN_BUFFER_INFO csbi; 582 | 583 | GetConsoleScreenBufferInfo(hConsoleOutput, &csbi); 584 | WriteConsoleOutputCharacter(hConsoleOutput, str, len, csbi.dwCursorPosition, &numberOfCharsWritten); 585 | #else // _WIN32 || USE_ANSI 586 | RLUTIL_PRINT(str); 587 | #ifdef __cplusplus 588 | RLUTIL_PRINT("\033[" << len << 'D'); 589 | #else // __cplusplus 590 | char buf[3 + 20 + 1]; // 20 = max length of 64-bit unsigned int when printed as dec 591 | sprintf(buf, "\033[%uD", len); 592 | RLUTIL_PRINT(buf); 593 | #endif // __cplusplus 594 | #endif // _WIN32 || USE_ANSI 595 | } 596 | 597 | /// Function: setChar 598 | /// Sets the character at the cursor without advancing the cursor 599 | RLUTIL_INLINE void setChar(char ch) { 600 | const char buf[] = {ch, 0}; 601 | setString(buf); 602 | } 603 | 604 | /// Function: setCursorVisibility 605 | /// Shows/hides the cursor. 606 | RLUTIL_INLINE void setCursorVisibility(char visible) { 607 | #if defined(_WIN32) && !defined(RLUTIL_USE_ANSI) 608 | HANDLE hConsoleOutput = GetStdHandle( STD_OUTPUT_HANDLE ); 609 | CONSOLE_CURSOR_INFO structCursorInfo; 610 | GetConsoleCursorInfo( hConsoleOutput, &structCursorInfo ); // Get current cursor size 611 | structCursorInfo.bVisible = (visible ? TRUE : FALSE); 612 | SetConsoleCursorInfo( hConsoleOutput, &structCursorInfo ); 613 | #else // _WIN32 || USE_ANSI 614 | RLUTIL_PRINT((visible ? ANSI_CURSOR_SHOW : ANSI_CURSOR_HIDE)); 615 | #endif // _WIN32 || USE_ANSI 616 | } 617 | 618 | /// Function: hidecursor 619 | /// Hides the cursor. 620 | RLUTIL_INLINE void hidecursor(void) { 621 | setCursorVisibility(0); 622 | } 623 | 624 | /// Function: showcursor 625 | /// Shows the cursor. 626 | RLUTIL_INLINE void showcursor(void) { 627 | setCursorVisibility(1); 628 | } 629 | 630 | /// Function: msleep 631 | /// Waits given number of milliseconds before continuing. 632 | RLUTIL_INLINE void msleep(unsigned int ms) { 633 | #ifdef _WIN32 634 | Sleep(ms); 635 | #else 636 | // usleep argument must be under 1 000 000 637 | if (ms > 1000) sleep(ms/1000000); 638 | usleep((ms % 1000000) * 1000); 639 | #endif 640 | } 641 | 642 | /// Function: trows 643 | /// Get the number of rows in the terminal window or -1 on error. 644 | RLUTIL_INLINE int trows(void) { 645 | #ifdef _WIN32 646 | CONSOLE_SCREEN_BUFFER_INFO csbi; 647 | if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) 648 | return -1; 649 | else 650 | return csbi.srWindow.Bottom - csbi.srWindow.Top + 1; // Window height 651 | // return csbi.dwSize.Y; // Buffer height 652 | #else 653 | #ifdef TIOCGSIZE 654 | struct ttysize ts; 655 | ioctl(STDIN_FILENO, TIOCGSIZE, &ts); 656 | return ts.ts_lines; 657 | #elif defined(TIOCGWINSZ) 658 | struct winsize ts; 659 | ioctl(STDIN_FILENO, TIOCGWINSZ, &ts); 660 | return ts.ws_row; 661 | #else // TIOCGSIZE 662 | return -1; 663 | #endif // TIOCGSIZE 664 | #endif // _WIN32 665 | } 666 | 667 | /// Function: tcols 668 | /// Get the number of columns in the terminal window or -1 on error. 669 | RLUTIL_INLINE int tcols(void) { 670 | #ifdef _WIN32 671 | CONSOLE_SCREEN_BUFFER_INFO csbi; 672 | if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) 673 | return -1; 674 | else 675 | return csbi.srWindow.Right - csbi.srWindow.Left + 1; // Window width 676 | // return csbi.dwSize.X; // Buffer width 677 | #else 678 | #ifdef TIOCGSIZE 679 | struct ttysize ts; 680 | ioctl(STDIN_FILENO, TIOCGSIZE, &ts); 681 | return ts.ts_cols; 682 | #elif defined(TIOCGWINSZ) 683 | struct winsize ts; 684 | ioctl(STDIN_FILENO, TIOCGWINSZ, &ts); 685 | return ts.ws_col; 686 | #else // TIOCGSIZE 687 | return -1; 688 | #endif // TIOCGSIZE 689 | #endif // _WIN32 690 | } 691 | 692 | /// Function: anykey 693 | /// Waits until a key is pressed. 694 | /// In C++, it either takes no arguments 695 | /// or a template-type-argument-deduced 696 | /// argument. 697 | /// In C, it takes a const char* representing 698 | /// the message to be displayed, or NULL 699 | /// for no message. 700 | #ifdef __cplusplus 701 | RLUTIL_INLINE void anykey() { 702 | getch(); 703 | } 704 | 705 | template void anykey(const T& msg) { 706 | RLUTIL_PRINT(msg); 707 | #else 708 | RLUTIL_INLINE void anykey(RLUTIL_STRING_T msg) { 709 | if (msg) 710 | RLUTIL_PRINT(msg); 711 | #endif // __cplusplus 712 | getch(); 713 | } 714 | 715 | // Classes are here at the end so that documentation is pretty. 716 | 717 | #ifdef __cplusplus 718 | /// Class: CursorHider 719 | /// RAII OOP wrapper for . 720 | /// Hides the cursor and shows it again 721 | /// when the object goes out of scope. 722 | struct CursorHider { 723 | CursorHider() { hidecursor(); } 724 | ~CursorHider() { showcursor(); } 725 | }; 726 | 727 | } // namespace rlutil 728 | #endif 729 | -------------------------------------------------------------------------------- /tests/file_open_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include "cbehave.h" 5 | #include "util.h" 6 | 7 | 8 | FEATURE(file_open, "File open") 9 | SCENARIO("Open file in current directory") 10 | GIVEN("a file in the current directory") 11 | char name[4096]; 12 | make_temp_file("temp_file_", name); 13 | WHEN("we open it") 14 | tinydir_file file; 15 | int r = tinydir_file_open(&file, name); 16 | THEN("the result should be successful") 17 | SHOULD_INT_EQUAL(r, 0); 18 | remove(name); 19 | SCENARIO_END 20 | SCENARIO("Open file with a very long filename") 21 | GIVEN("a file with a long filename in a folder with a long filename") 22 | char folder[4096]; 23 | make_temp_dir("temp_dir_", folder); 24 | ASSERT(strlen(folder) > 0, 1); 25 | strcat(folder, "/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); 26 | ASSERT(mkdir(folder, 0700) == 0, 0); 27 | strcat(folder, "/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); 28 | ASSERT(mkdir(folder, 0700) == 0, 0); 29 | WHEN("we open it") 30 | tinydir_file file; 31 | int r = tinydir_file_open(&file, folder); 32 | THEN("the result should be successful") 33 | SHOULD_INT_EQUAL(r, 0); 34 | SCENARIO_END 35 | FEATURE_END 36 | 37 | CBEHAVE_RUN("File open:", TEST_FEATURE(file_open)) 38 | -------------------------------------------------------------------------------- /tests/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef _MSC_VER 4 | #define WIN32_LEAN_AND_MEAN 5 | #include 6 | #else 7 | #include 8 | #include 9 | #endif 10 | 11 | 12 | void make_temp_file(const char *prefix, char *out) 13 | { 14 | #ifdef _MSC_VER 15 | if (GetTempFileName(".", prefix, 0, out) != 0) 16 | { 17 | // Strip the ".\\" prefix 18 | if (strncmp(out, ".\\", 2) == 0) 19 | { 20 | memmove(out, out + 2, strlen(out)); 21 | } 22 | // Create file 23 | fclose(fopen(out, "w")); 24 | } 25 | #else 26 | sprintf(out, "%sXXXXXX", prefix); 27 | close(mkstemp(out)); 28 | #endif 29 | } 30 | 31 | void make_temp_dir(const char *prefix, char *out) 32 | { 33 | #ifdef _MSC_VER 34 | if (GetTempFileName(".", prefix, 0, out) != 0) 35 | { 36 | // Strip the ".\\" prefix 37 | if (strncmp(out, ".\\", 2) == 0) 38 | { 39 | memmove(out, out + 2, strlen(out)); 40 | } 41 | // Create file 42 | fclose(fopen(out, "w")); 43 | } 44 | #else 45 | sprintf(out, "%sXXXXXX", prefix); 46 | if (mkdtemp(out) == NULL) 47 | { 48 | out[0] = '\0'; 49 | } 50 | #endif 51 | } 52 | -------------------------------------------------------------------------------- /tests/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void make_temp_file(const char *prefix, char *out); 4 | void make_temp_dir(const char *prefix, char *out); 5 | 6 | #ifdef _MSC_VER 7 | #include 8 | #define mkdir(p, a) _mkdir(p) 9 | #elif defined(_WIN32) 10 | #define mkdir(p, a) mkdir(p) 11 | #endif 12 | -------------------------------------------------------------------------------- /tests/windows_unicode_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define UNICODE 4 | #include 5 | #include "cbehave.h" 6 | 7 | 8 | #define TEST_PATH L"windows_unicode_test" 9 | #define TEST_PATH_A "windows_unicode_test" 10 | 11 | void make_temp_file_utf16(const wchar_t *prefix, wchar_t *out) 12 | { 13 | if (GetTempFileNameW(TEST_PATH, prefix, 0, out) != 0) 14 | { 15 | // Create file 16 | fclose(_wfopen(out, L"w")); 17 | // Strip the ".\\" prefix 18 | if (wcsncmp(out, TEST_PATH L"\\", wcslen(TEST_PATH) + 1) == 0) 19 | { 20 | memmove(out, out + wcslen(TEST_PATH) + 1, wcslen(out)); 21 | } 22 | } 23 | } 24 | 25 | FEATURE(windows_unicode, "Windows Unicode") 26 | SCENARIO("Open directory with unicode file names") 27 | GIVEN("a directory with unicode file names") 28 | CreateDirectoryW(TEST_PATH, NULL); 29 | wchar_t name[4096]; 30 | make_temp_file_utf16(L"t📁", name); 31 | WHEN("we open the directory") 32 | tinydir_dir dir; 33 | int r = tinydir_open(&dir, TEST_PATH); 34 | THEN("the result should be successful") 35 | SHOULD_INT_EQUAL(r, 0); 36 | AND("iterating it, we should find the file") 37 | bool found = false; 38 | while (dir.has_next) 39 | { 40 | tinydir_file file; 41 | tinydir_readfile(&dir, &file); 42 | if (wcscmp((wchar_t *)file.name, name) == 0) 43 | { 44 | found = true; 45 | } 46 | tinydir_next(&dir); 47 | } 48 | SHOULD_BE_TRUE(found); 49 | tinydir_close(&dir); 50 | _wremove(name); 51 | RemoveDirectoryW(TEST_PATH); 52 | SCENARIO_END 53 | FEATURE_END 54 | 55 | CBEHAVE_RUN("Windows unicode:", TEST_FEATURE(windows_unicode)) 56 | -------------------------------------------------------------------------------- /tinydir.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013-2021, tinydir authors: 3 | - Cong Xu 4 | - Lautis Sun 5 | - Baudouin Feildel 6 | - Andargor 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions are met: 11 | 12 | 1. Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | 2. Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | #ifndef TINYDIR_H 30 | #define TINYDIR_H 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | #if ((defined _UNICODE) && !(defined UNICODE)) 37 | #define UNICODE 38 | #endif 39 | 40 | #if ((defined UNICODE) && !(defined _UNICODE)) 41 | #define _UNICODE 42 | #endif 43 | 44 | #include 45 | #include 46 | #include 47 | #ifdef _MSC_VER 48 | # ifndef WIN32_LEAN_AND_MEAN 49 | # define WIN32_LEAN_AND_MEAN 50 | # endif 51 | # include 52 | # include 53 | # pragma warning(push) 54 | # pragma warning (disable : 4996) 55 | #else 56 | # include 57 | # include 58 | # include 59 | # include 60 | #endif 61 | #ifdef __MINGW32__ 62 | # include 63 | #endif 64 | 65 | 66 | /* types */ 67 | 68 | /* Windows UNICODE wide character support */ 69 | #if defined _MSC_VER || defined __MINGW32__ 70 | # define _tinydir_char_t TCHAR 71 | # define TINYDIR_STRING(s) _TEXT(s) 72 | # define _tinydir_strlen _tcslen 73 | # define _tinydir_strcpy _tcscpy 74 | # define _tinydir_strcat _tcscat 75 | # define _tinydir_strcmp _tcscmp 76 | # define _tinydir_strrchr _tcsrchr 77 | # define _tinydir_strncmp _tcsncmp 78 | #else 79 | # define _tinydir_char_t char 80 | # define TINYDIR_STRING(s) s 81 | # define _tinydir_strlen strlen 82 | # define _tinydir_strcpy strcpy 83 | # define _tinydir_strcat strcat 84 | # define _tinydir_strcmp strcmp 85 | # define _tinydir_strrchr strrchr 86 | # define _tinydir_strncmp strncmp 87 | #endif 88 | 89 | #if (defined _MSC_VER || defined __MINGW32__) 90 | # include 91 | # define _TINYDIR_PATH_MAX MAX_PATH 92 | #elif defined __linux__ 93 | # include 94 | # ifdef PATH_MAX 95 | # define _TINYDIR_PATH_MAX PATH_MAX 96 | # endif 97 | #elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 98 | # include 99 | # if defined(BSD) 100 | # include 101 | # ifdef PATH_MAX 102 | # define _TINYDIR_PATH_MAX PATH_MAX 103 | # endif 104 | # endif 105 | #endif 106 | 107 | #ifndef _TINYDIR_PATH_MAX 108 | #define _TINYDIR_PATH_MAX 4096 109 | #endif 110 | 111 | #ifdef _MSC_VER 112 | /* extra chars for the "\\*" mask */ 113 | # define _TINYDIR_PATH_EXTRA 2 114 | #else 115 | # define _TINYDIR_PATH_EXTRA 0 116 | #endif 117 | 118 | #define _TINYDIR_FILENAME_MAX 256 119 | 120 | #if (defined _MSC_VER || defined __MINGW32__) 121 | #define _TINYDIR_DRIVE_MAX 3 122 | #endif 123 | 124 | #ifdef _MSC_VER 125 | # define _TINYDIR_FUNC static __inline 126 | #elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L 127 | # define _TINYDIR_FUNC static __inline__ 128 | #elif defined(__cplusplus) 129 | # define _TINYDIR_FUNC static inline 130 | #elif defined(__GNUC__) 131 | /* Suppress unused function warning */ 132 | # define _TINYDIR_FUNC __attribute__((unused)) static 133 | #else 134 | # define _TINYDIR_FUNC static 135 | #endif 136 | 137 | #if defined(i386) || defined(__i386__) || defined(__i386) || defined(_M_IX86) 138 | #ifdef _MSC_VER 139 | # define _TINYDIR_CDECL __cdecl 140 | #else 141 | # define _TINYDIR_CDECL __attribute__((cdecl)) 142 | #endif 143 | #else 144 | # define _TINYDIR_CDECL 145 | #endif 146 | 147 | /* readdir_r usage; define TINYDIR_USE_READDIR_R to use it (if supported) */ 148 | #ifdef TINYDIR_USE_READDIR_R 149 | 150 | /* readdir_r is a POSIX-only function, and may not be available under various 151 | * environments/settings, e.g. MinGW. Use readdir fallback */ 152 | #if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE ||\ 153 | _POSIX_SOURCE 154 | # define _TINYDIR_HAS_READDIR_R 155 | #endif 156 | #if _POSIX_C_SOURCE >= 200112L 157 | # define _TINYDIR_HAS_FPATHCONF 158 | # include 159 | #endif 160 | #if _BSD_SOURCE || _SVID_SOURCE || \ 161 | (_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700) 162 | # define _TINYDIR_HAS_DIRFD 163 | # include 164 | #endif 165 | #if defined _TINYDIR_HAS_FPATHCONF && defined _TINYDIR_HAS_DIRFD &&\ 166 | defined _PC_NAME_MAX 167 | # define _TINYDIR_USE_FPATHCONF 168 | #endif 169 | #if defined __MINGW32__ || !defined _TINYDIR_HAS_READDIR_R ||\ 170 | !(defined _TINYDIR_USE_FPATHCONF || defined NAME_MAX) 171 | # define _TINYDIR_USE_READDIR 172 | #endif 173 | 174 | /* Use readdir by default */ 175 | #else 176 | # define _TINYDIR_USE_READDIR 177 | #endif 178 | 179 | /* MINGW32 has two versions of dirent, ASCII and UNICODE*/ 180 | #ifndef _MSC_VER 181 | #if (defined __MINGW32__) && (defined _UNICODE) 182 | #define _TINYDIR_DIR _WDIR 183 | #define _tinydir_dirent _wdirent 184 | #define _tinydir_opendir _wopendir 185 | #define _tinydir_readdir _wreaddir 186 | #define _tinydir_closedir _wclosedir 187 | #else 188 | #define _TINYDIR_DIR DIR 189 | #define _tinydir_dirent dirent 190 | #define _tinydir_opendir opendir 191 | #define _tinydir_readdir readdir 192 | #define _tinydir_closedir closedir 193 | #endif 194 | #endif 195 | 196 | /* Allow user to use a custom allocator by defining _TINYDIR_MALLOC and _TINYDIR_FREE. */ 197 | #if defined(_TINYDIR_MALLOC) && defined(_TINYDIR_FREE) 198 | #elif !defined(_TINYDIR_MALLOC) && !defined(_TINYDIR_FREE) 199 | #else 200 | #error "Either define both alloc and free or none of them!" 201 | #endif 202 | 203 | #if !defined(_TINYDIR_MALLOC) 204 | #define _TINYDIR_MALLOC(_size) malloc(_size) 205 | #define _TINYDIR_FREE(_ptr) free(_ptr) 206 | #endif /* !defined(_TINYDIR_MALLOC) */ 207 | 208 | typedef struct tinydir_file 209 | { 210 | _tinydir_char_t path[_TINYDIR_PATH_MAX]; 211 | _tinydir_char_t name[_TINYDIR_FILENAME_MAX]; 212 | _tinydir_char_t *extension; 213 | int is_dir; 214 | int is_reg; 215 | 216 | #ifndef _MSC_VER 217 | #ifdef __MINGW32__ 218 | struct _stat _s; 219 | #else 220 | struct stat _s; 221 | #endif 222 | #endif 223 | } tinydir_file; 224 | 225 | typedef struct tinydir_dir 226 | { 227 | _tinydir_char_t path[_TINYDIR_PATH_MAX]; 228 | int has_next; 229 | size_t n_files; 230 | 231 | tinydir_file *_files; 232 | #ifdef _MSC_VER 233 | HANDLE _h; 234 | WIN32_FIND_DATA _f; 235 | #else 236 | _TINYDIR_DIR *_d; 237 | struct _tinydir_dirent *_e; 238 | #ifndef _TINYDIR_USE_READDIR 239 | struct _tinydir_dirent *_ep; 240 | #endif 241 | #endif 242 | } tinydir_dir; 243 | 244 | 245 | /* declarations */ 246 | 247 | _TINYDIR_FUNC 248 | int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path); 249 | _TINYDIR_FUNC 250 | int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path); 251 | _TINYDIR_FUNC 252 | void tinydir_close(tinydir_dir *dir); 253 | 254 | _TINYDIR_FUNC 255 | int tinydir_next(tinydir_dir *dir); 256 | _TINYDIR_FUNC 257 | int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file); 258 | _TINYDIR_FUNC 259 | int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i); 260 | _TINYDIR_FUNC 261 | int tinydir_open_subdir_n(tinydir_dir *dir, size_t i); 262 | 263 | _TINYDIR_FUNC 264 | int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path); 265 | _TINYDIR_FUNC 266 | void _tinydir_get_ext(tinydir_file *file); 267 | _TINYDIR_FUNC 268 | int _TINYDIR_CDECL _tinydir_file_cmp(const void *a, const void *b); 269 | #ifndef _MSC_VER 270 | #ifndef _TINYDIR_USE_READDIR 271 | _TINYDIR_FUNC 272 | size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp); 273 | #endif 274 | #endif 275 | 276 | 277 | /* definitions*/ 278 | 279 | _TINYDIR_FUNC 280 | int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path) 281 | { 282 | #ifndef _MSC_VER 283 | #ifndef _TINYDIR_USE_READDIR 284 | int error; 285 | int size; /* using int size */ 286 | #endif 287 | #else 288 | _tinydir_char_t path_buf[_TINYDIR_PATH_MAX]; 289 | #endif 290 | _tinydir_char_t *pathp; 291 | 292 | if (dir == NULL || path == NULL || _tinydir_strlen(path) == 0) 293 | { 294 | errno = EINVAL; 295 | return -1; 296 | } 297 | if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) 298 | { 299 | errno = ENAMETOOLONG; 300 | return -1; 301 | } 302 | 303 | /* initialise dir */ 304 | dir->_files = NULL; 305 | #ifdef _MSC_VER 306 | dir->_h = INVALID_HANDLE_VALUE; 307 | #else 308 | dir->_d = NULL; 309 | #ifndef _TINYDIR_USE_READDIR 310 | dir->_ep = NULL; 311 | #endif 312 | #endif 313 | tinydir_close(dir); 314 | 315 | _tinydir_strcpy(dir->path, path); 316 | /* Remove trailing slashes */ 317 | pathp = &dir->path[_tinydir_strlen(dir->path) - 1]; 318 | while (pathp != dir->path && (*pathp == TINYDIR_STRING('\\') || *pathp == TINYDIR_STRING('/'))) 319 | { 320 | *pathp = TINYDIR_STRING('\0'); 321 | pathp++; 322 | } 323 | #ifdef _MSC_VER 324 | _tinydir_strcpy(path_buf, dir->path); 325 | _tinydir_strcat(path_buf, TINYDIR_STRING("\\*")); 326 | #if (defined WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) 327 | dir->_h = FindFirstFileEx(path_buf, FindExInfoStandard, &dir->_f, FindExSearchNameMatch, NULL, 0); 328 | #else 329 | dir->_h = FindFirstFile(path_buf, &dir->_f); 330 | #endif 331 | if (dir->_h == INVALID_HANDLE_VALUE) 332 | { 333 | errno = ENOENT; 334 | #else 335 | dir->_d = _tinydir_opendir(path); 336 | if (dir->_d == NULL) 337 | { 338 | #endif 339 | goto bail; 340 | } 341 | 342 | /* read first file */ 343 | dir->has_next = 1; 344 | #ifndef _MSC_VER 345 | #ifdef _TINYDIR_USE_READDIR 346 | dir->_e = _tinydir_readdir(dir->_d); 347 | #else 348 | /* allocate dirent buffer for readdir_r */ 349 | size = _tinydir_dirent_buf_size(dir->_d); /* conversion to int */ 350 | if (size == -1) return -1; 351 | dir->_ep = (struct _tinydir_dirent*)_TINYDIR_MALLOC(size); 352 | if (dir->_ep == NULL) return -1; 353 | 354 | error = readdir_r(dir->_d, dir->_ep, &dir->_e); 355 | if (error != 0) return -1; 356 | #endif 357 | if (dir->_e == NULL) 358 | { 359 | dir->has_next = 0; 360 | } 361 | #endif 362 | 363 | return 0; 364 | 365 | bail: 366 | tinydir_close(dir); 367 | return -1; 368 | } 369 | 370 | _TINYDIR_FUNC 371 | int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path) 372 | { 373 | /* Count the number of files first, to pre-allocate the files array */ 374 | size_t n_files = 0; 375 | if (tinydir_open(dir, path) == -1) 376 | { 377 | return -1; 378 | } 379 | while (dir->has_next) 380 | { 381 | n_files++; 382 | if (tinydir_next(dir) == -1) 383 | { 384 | goto bail; 385 | } 386 | } 387 | tinydir_close(dir); 388 | 389 | if (n_files == 0 || tinydir_open(dir, path) == -1) 390 | { 391 | return -1; 392 | } 393 | 394 | dir->n_files = 0; 395 | dir->_files = (tinydir_file *)_TINYDIR_MALLOC(sizeof *dir->_files * n_files); 396 | if (dir->_files == NULL) 397 | { 398 | goto bail; 399 | } 400 | while (dir->has_next) 401 | { 402 | tinydir_file *p_file; 403 | dir->n_files++; 404 | 405 | p_file = &dir->_files[dir->n_files - 1]; 406 | if (tinydir_readfile(dir, p_file) == -1) 407 | { 408 | goto bail; 409 | } 410 | 411 | if (tinydir_next(dir) == -1) 412 | { 413 | goto bail; 414 | } 415 | 416 | /* Just in case the number of files has changed between the first and 417 | second reads, terminate without writing into unallocated memory */ 418 | if (dir->n_files == n_files) 419 | { 420 | break; 421 | } 422 | } 423 | 424 | qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp); 425 | 426 | return 0; 427 | 428 | bail: 429 | tinydir_close(dir); 430 | return -1; 431 | } 432 | 433 | _TINYDIR_FUNC 434 | void tinydir_close(tinydir_dir *dir) 435 | { 436 | if (dir == NULL) 437 | { 438 | return; 439 | } 440 | 441 | memset(dir->path, 0, sizeof(dir->path)); 442 | dir->has_next = 0; 443 | dir->n_files = 0; 444 | _TINYDIR_FREE(dir->_files); 445 | dir->_files = NULL; 446 | #ifdef _MSC_VER 447 | if (dir->_h != INVALID_HANDLE_VALUE) 448 | { 449 | FindClose(dir->_h); 450 | } 451 | dir->_h = INVALID_HANDLE_VALUE; 452 | #else 453 | if (dir->_d) 454 | { 455 | _tinydir_closedir(dir->_d); 456 | } 457 | dir->_d = NULL; 458 | dir->_e = NULL; 459 | #ifndef _TINYDIR_USE_READDIR 460 | _TINYDIR_FREE(dir->_ep); 461 | dir->_ep = NULL; 462 | #endif 463 | #endif 464 | } 465 | 466 | _TINYDIR_FUNC 467 | int tinydir_next(tinydir_dir *dir) 468 | { 469 | if (dir == NULL) 470 | { 471 | errno = EINVAL; 472 | return -1; 473 | } 474 | if (!dir->has_next) 475 | { 476 | errno = ENOENT; 477 | return -1; 478 | } 479 | 480 | #ifdef _MSC_VER 481 | if (FindNextFile(dir->_h, &dir->_f) == 0) 482 | #else 483 | #ifdef _TINYDIR_USE_READDIR 484 | dir->_e = _tinydir_readdir(dir->_d); 485 | #else 486 | if (dir->_ep == NULL) 487 | { 488 | return -1; 489 | } 490 | if (readdir_r(dir->_d, dir->_ep, &dir->_e) != 0) 491 | { 492 | return -1; 493 | } 494 | #endif 495 | if (dir->_e == NULL) 496 | #endif 497 | { 498 | dir->has_next = 0; 499 | #ifdef _MSC_VER 500 | if (GetLastError() != ERROR_SUCCESS && 501 | GetLastError() != ERROR_NO_MORE_FILES) 502 | { 503 | tinydir_close(dir); 504 | errno = EIO; 505 | return -1; 506 | } 507 | #endif 508 | } 509 | 510 | return 0; 511 | } 512 | 513 | _TINYDIR_FUNC 514 | int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file) 515 | { 516 | const _tinydir_char_t *filename; 517 | if (dir == NULL || file == NULL) 518 | { 519 | errno = EINVAL; 520 | return -1; 521 | } 522 | #ifdef _MSC_VER 523 | if (dir->_h == INVALID_HANDLE_VALUE) 524 | #else 525 | if (dir->_e == NULL) 526 | #endif 527 | { 528 | errno = ENOENT; 529 | return -1; 530 | } 531 | filename = 532 | #ifdef _MSC_VER 533 | dir->_f.cFileName; 534 | #else 535 | dir->_e->d_name; 536 | #endif 537 | if (_tinydir_strlen(dir->path) + 538 | _tinydir_strlen(filename) + 1 + _TINYDIR_PATH_EXTRA >= 539 | _TINYDIR_PATH_MAX) 540 | { 541 | /* the path for the file will be too long */ 542 | errno = ENAMETOOLONG; 543 | return -1; 544 | } 545 | if (_tinydir_strlen(filename) >= _TINYDIR_FILENAME_MAX) 546 | { 547 | errno = ENAMETOOLONG; 548 | return -1; 549 | } 550 | 551 | _tinydir_strcpy(file->path, dir->path); 552 | if (_tinydir_strcmp(dir->path, TINYDIR_STRING("/")) != 0) 553 | _tinydir_strcat(file->path, TINYDIR_STRING("/")); 554 | _tinydir_strcpy(file->name, filename); 555 | _tinydir_strcat(file->path, filename); 556 | #ifndef _MSC_VER 557 | #ifdef __MINGW32__ 558 | if (_tstat( 559 | #elif (defined _BSD_SOURCE) || (defined _DEFAULT_SOURCE) \ 560 | || ((defined _XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500)) \ 561 | || ((defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L)) \ 562 | || ((defined __APPLE__) && (defined __MACH__)) \ 563 | || (defined BSD) 564 | if (lstat( 565 | #else 566 | if (stat( 567 | #endif 568 | file->path, &file->_s) == -1) 569 | { 570 | return -1; 571 | } 572 | #endif 573 | _tinydir_get_ext(file); 574 | 575 | file->is_dir = 576 | #ifdef _MSC_VER 577 | !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); 578 | #else 579 | S_ISDIR(file->_s.st_mode); 580 | #endif 581 | file->is_reg = 582 | #ifdef _MSC_VER 583 | !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) || 584 | ( 585 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) && 586 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && 587 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) && 588 | #ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM 589 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) && 590 | #endif 591 | #ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA 592 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) && 593 | #endif 594 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) && 595 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY)); 596 | #else 597 | S_ISREG(file->_s.st_mode); 598 | #endif 599 | 600 | return 0; 601 | } 602 | 603 | _TINYDIR_FUNC 604 | int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i) 605 | { 606 | if (dir == NULL || file == NULL) 607 | { 608 | errno = EINVAL; 609 | return -1; 610 | } 611 | if (i >= dir->n_files) 612 | { 613 | errno = ENOENT; 614 | return -1; 615 | } 616 | 617 | memcpy(file, &dir->_files[i], sizeof(tinydir_file)); 618 | _tinydir_get_ext(file); 619 | 620 | return 0; 621 | } 622 | 623 | _TINYDIR_FUNC 624 | int tinydir_open_subdir_n(tinydir_dir *dir, size_t i) 625 | { 626 | _tinydir_char_t path[_TINYDIR_PATH_MAX]; 627 | if (dir == NULL) 628 | { 629 | errno = EINVAL; 630 | return -1; 631 | } 632 | if (i >= dir->n_files || !dir->_files[i].is_dir) 633 | { 634 | errno = ENOENT; 635 | return -1; 636 | } 637 | 638 | _tinydir_strcpy(path, dir->_files[i].path); 639 | tinydir_close(dir); 640 | if (tinydir_open_sorted(dir, path) == -1) 641 | { 642 | return -1; 643 | } 644 | 645 | return 0; 646 | } 647 | 648 | /* Open a single file given its path */ 649 | _TINYDIR_FUNC 650 | int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path) 651 | { 652 | tinydir_dir dir; 653 | int result = 0; 654 | int found = 0; 655 | _tinydir_char_t dir_name_buf[_TINYDIR_PATH_MAX]; 656 | _tinydir_char_t file_name_buf[_TINYDIR_PATH_MAX]; 657 | _tinydir_char_t *dir_name; 658 | _tinydir_char_t *base_name; 659 | #if (defined _MSC_VER || defined __MINGW32__) 660 | _tinydir_char_t drive_buf[_TINYDIR_PATH_MAX]; 661 | _tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX]; 662 | #endif 663 | 664 | if (file == NULL || path == NULL || _tinydir_strlen(path) == 0) 665 | { 666 | errno = EINVAL; 667 | return -1; 668 | } 669 | if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) 670 | { 671 | errno = ENAMETOOLONG; 672 | return -1; 673 | } 674 | 675 | /* Get the parent path */ 676 | #if (defined _MSC_VER || defined __MINGW32__) 677 | #if ((defined _MSC_VER) && (_MSC_VER >= 1400)) 678 | errno = _tsplitpath_s( 679 | path, 680 | drive_buf, _TINYDIR_DRIVE_MAX, 681 | dir_name_buf, _TINYDIR_FILENAME_MAX, 682 | file_name_buf, _TINYDIR_FILENAME_MAX, 683 | ext_buf, _TINYDIR_FILENAME_MAX); 684 | #else 685 | _tsplitpath( 686 | path, 687 | drive_buf, 688 | dir_name_buf, 689 | file_name_buf, 690 | ext_buf); 691 | #endif 692 | 693 | if (errno) 694 | { 695 | return -1; 696 | } 697 | 698 | /* _splitpath_s not work fine with only filename and widechar support */ 699 | #ifdef _UNICODE 700 | if (drive_buf[0] == L'\xFEFE') 701 | drive_buf[0] = '\0'; 702 | if (dir_name_buf[0] == L'\xFEFE') 703 | dir_name_buf[0] = '\0'; 704 | #endif 705 | 706 | /* Emulate the behavior of dirname by returning "." for dir name if it's 707 | empty */ 708 | if (drive_buf[0] == '\0' && dir_name_buf[0] == '\0') 709 | { 710 | _tinydir_strcpy(dir_name_buf, TINYDIR_STRING(".")); 711 | } 712 | /* Concatenate the drive letter and dir name to form full dir name */ 713 | _tinydir_strcat(drive_buf, dir_name_buf); 714 | dir_name = drive_buf; 715 | /* Concatenate the file name and extension to form base name */ 716 | _tinydir_strcat(file_name_buf, ext_buf); 717 | base_name = file_name_buf; 718 | #else 719 | _tinydir_strcpy(dir_name_buf, path); 720 | dir_name = dirname(dir_name_buf); 721 | _tinydir_strcpy(file_name_buf, path); 722 | base_name = basename(file_name_buf); 723 | #endif 724 | 725 | /* Special case: if the path is a root dir, open the parent dir as the file */ 726 | #if (defined _MSC_VER || defined __MINGW32__) 727 | if (_tinydir_strlen(base_name) == 0) 728 | #else 729 | if ((_tinydir_strcmp(base_name, TINYDIR_STRING("/"))) == 0) 730 | #endif 731 | { 732 | memset(file, 0, sizeof * file); 733 | file->is_dir = 1; 734 | file->is_reg = 0; 735 | _tinydir_strcpy(file->path, dir_name); 736 | file->extension = file->path + _tinydir_strlen(file->path); 737 | return 0; 738 | } 739 | 740 | /* Open the parent directory */ 741 | if (tinydir_open(&dir, dir_name) == -1) 742 | { 743 | return -1; 744 | } 745 | 746 | /* Read through the parent directory and look for the file */ 747 | while (dir.has_next) 748 | { 749 | if (tinydir_readfile(&dir, file) == -1) 750 | { 751 | result = -1; 752 | goto bail; 753 | } 754 | if (_tinydir_strcmp(file->name, base_name) == 0) 755 | { 756 | /* File found */ 757 | found = 1; 758 | break; 759 | } 760 | tinydir_next(&dir); 761 | } 762 | if (!found) 763 | { 764 | result = -1; 765 | errno = ENOENT; 766 | } 767 | 768 | bail: 769 | tinydir_close(&dir); 770 | return result; 771 | } 772 | 773 | _TINYDIR_FUNC 774 | void _tinydir_get_ext(tinydir_file *file) 775 | { 776 | _tinydir_char_t *period = _tinydir_strrchr(file->name, TINYDIR_STRING('.')); 777 | if (period == NULL) 778 | { 779 | file->extension = &(file->name[_tinydir_strlen(file->name)]); 780 | } 781 | else 782 | { 783 | file->extension = period + 1; 784 | } 785 | } 786 | 787 | _TINYDIR_FUNC 788 | int _TINYDIR_CDECL _tinydir_file_cmp(const void *a, const void *b) 789 | { 790 | const tinydir_file *fa = (const tinydir_file *)a; 791 | const tinydir_file *fb = (const tinydir_file *)b; 792 | if (fa->is_dir != fb->is_dir) 793 | { 794 | return -(fa->is_dir - fb->is_dir); 795 | } 796 | return _tinydir_strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX); 797 | } 798 | 799 | #ifndef _MSC_VER 800 | #ifndef _TINYDIR_USE_READDIR 801 | /* 802 | The following authored by Ben Hutchings 803 | from https://womble.decadent.org.uk/readdir_r-advisory.html 804 | */ 805 | /* Calculate the required buffer size (in bytes) for directory * 806 | * entries read from the given directory handle. Return -1 if this * 807 | * this cannot be done. * 808 | * * 809 | * This code does not trust values of NAME_MAX that are less than * 810 | * 255, since some systems (including at least HP-UX) incorrectly * 811 | * define it to be a smaller value. */ 812 | _TINYDIR_FUNC 813 | size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp) 814 | { 815 | long name_max; 816 | size_t name_end; 817 | /* parameter may be unused */ 818 | (void)dirp; 819 | 820 | #if defined _TINYDIR_USE_FPATHCONF 821 | name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX); 822 | if (name_max == -1) 823 | #if defined(NAME_MAX) 824 | name_max = (NAME_MAX > 255) ? NAME_MAX : 255; 825 | #else 826 | return (size_t)(-1); 827 | #endif 828 | #elif defined(NAME_MAX) 829 | name_max = (NAME_MAX > 255) ? NAME_MAX : 255; 830 | #else 831 | #error "buffer size for readdir_r cannot be determined" 832 | #endif 833 | name_end = (size_t)offsetof(struct _tinydir_dirent, d_name) + name_max + 1; 834 | return (name_end > sizeof(struct _tinydir_dirent) ? 835 | name_end : sizeof(struct _tinydir_dirent)); 836 | } 837 | #endif 838 | #endif 839 | 840 | #ifdef __cplusplus 841 | } 842 | #endif 843 | 844 | # if defined (_MSC_VER) 845 | # pragma warning(pop) 846 | # endif 847 | 848 | #endif 849 | --------------------------------------------------------------------------------