LCOV - code coverage report
Current view: top level - include/ioutils - fileUtils.hpp (source / functions) Coverage Total Hit
Test: mxlib Lines: 93.8 % 113 106
Test Date: 2026-08-30 22:23:37 Functions: 100.0 % 4 4

            Line data    Source code
       1              : 
       2              : /** \file fileUtils.hpp
       3              :  * \brief Declarations of utilities for working with files
       4              :  *
       5              :  * \author Jared R. Males (jaredmales@gmail.com)
       6              :  *
       7              :  * \ingroup fileutils
       8              :  *
       9              :  */
      10              : 
      11              : //***********************************************************************//
      12              : // Copyright 2015-2020 Jared R. Males (jaredmales@gmail.com)
      13              : //
      14              : // This file is part of mxlib.
      15              : //
      16              : // mxlib is free software: you can redistribute it and/or modify
      17              : // it under the terms of the GNU General Public License as published by
      18              : // the Free Software Foundation, either version 3 of the License, or
      19              : // (at your option) any later version.
      20              : //
      21              : // mxlib is distributed in the hope that it will be useful,
      22              : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      23              : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      24              : // GNU General Public License for more details.
      25              : //
      26              : // You should have received a copy of the GNU General Public License
      27              : // along with mxlib.  If not, see <http://www.gnu.org/licenses/>.
      28              : //***********************************************************************//
      29              : 
      30              : #ifndef ioutils_fileUtils_hpp
      31              : #define ioutils_fileUtils_hpp
      32              : 
      33              : #include <string>
      34              : #include <vector>
      35              : #include <filesystem>
      36              : #include <algorithm>
      37              : 
      38              : #include "../mxlib.hpp"
      39              : 
      40              : namespace mx
      41              : {
      42              : namespace ioutils
      43              : {
      44              : 
      45              : #ifndef ioutils_fileUtils_detail_hpp
      46              : #define ioutils_fileUtils_detail_hpp
      47              : namespace fileUtilsDetail
      48              : {
      49              : 
      50              : /** \cond */
      51              : /// File-utility stages exposed to deterministic exception tests.
      52              : enum class operation
      53              : {
      54              :     stringToPath,
      55              :     getFileNames,
      56              :     createDirectories
      57              : };
      58              : 
      59              : /// Signature of the resettable file-utility operation hook.
      60              : using operationHookT = void ( * )( operation );
      61              : 
      62              : /// Access the process-wide file-utility operation hook.
      63              : operationHookT &operationHook();
      64              : 
      65              : /// Signature of the resettable file-utility error-code hook.
      66              : using errorCodeHookT = void ( * )( operation, std::error_code & );
      67              : 
      68              : /// Access the process-wide file-utility error-code hook.
      69              : errorCodeHookT &errorCodeHook();
      70              : /** \endcond */
      71              : 
      72              : } // namespace fileUtilsDetail
      73              : #endif // ioutils_fileUtils_detail_hpp
      74              : 
      75              : #ifdef MXLIBTEST_NAMESPACE
      76              : namespace MXLIBTEST_NAMESPACE
      77              : {
      78              : #endif
      79              : 
      80              : /** \addtogroup fileutils
      81              :  * @{
      82              :  */
      83              : 
      84              : /// Convert a string to a path, handling exceptions.
      85              : /** Wrapper for `path = str` assignment that handles exceptions
      86              :  * and implements mxlib standard error handling.
      87              :  *
      88              :  * \returns error_t::noerror if no exceptions
      89              :  * \returns error_t::std_bad_alloc if std::bad_alloc is caught
      90              :  * \returns error_t::std_filesystem_error if std::filesystem::filesystem_error is caught
      91              :  * \returns error_t::std_exception if any other exceptions are caught
      92              :  *
      93              :  * \throws a nested mx::exception for any uncaught exceptions.
      94              :  */
      95              : template <class verboseT>
      96              : error_t string2path( std::filesystem::path &path, const std::string &str );
      97              : 
      98              : /// Check if a path exists
      99              : /**
     100              :  * \returns true if the path exists and no errors occur
     101              :  * \returns false otherwise
     102              :  */
     103              : template <class verboseT = verbose::d>
     104              : bool exists( const std::string &path, /**< [in] the path to check for existence */
     105              :              mx::error_t &errc        /**< [out] error code. Typically convereted as errno from std::filesystem*/
     106              : );
     107              : 
     108              : /// Check if a path exists and is a directory
     109              : /**
     110              :  * \returns true only if \p dir both exists and is a directory, and no errors occur
     111              :  * \returns false otherwise
     112              :  */
     113              : template <class verboseT = verbose::d>
     114              : bool dir_exists_is( const std::string &dir, /**< [in] the path to check */
     115              :                     mx::error_t &errc       /**< [out] error code. Typically convereted as errno from std::filesystem*/
     116              : );
     117              : 
     118              : /// Create a directory or directories
     119              : /** This will create any directories in path that don't exist.  It silently ignores already existing directories.
     120              :  *
     121              :  * \returns error_t::noerror on success, indicating the directories were created or already existed.
     122              :  * \returns other codes, error_t::exxxx (from errno) or error_t::filesystem, on errors.
     123              :  */
     124              : error_t createDirectories( const std::string &path /**< [in] the path of the directory(ies)to create */ );
     125              : 
     126              : /// Get the stem of the filename
     127              : /**
     128              :  * \returns the stem for the filename, that is without the path or extension
     129              :  */
     130              : std::string pathStem( const std::string &fname );
     131              : 
     132              : /// Get the base filename
     133              : /**
     134              :  * \returns the filename, including the extension but without the path
     135              :  */
     136              : std::string pathFilename( const std::string &fname );
     137              : 
     138              : /// Get the parent path from a filename
     139              : /**
     140              :  * \returns the parent path of the file
     141              :  */
     142              : std::string parentPath( const std::string &fname );
     143              : 
     144              : /// Get a list of file names from the specified directory, specifying a prefix, a substring to match, and an extension
     145              : /**
     146              :  * \returns mx::error_t::success on success
     147              :  * \returns mx::error_t::invalidarg if \p directory is not a directory
     148              :  * \returns mx::error_t::dirnotfound if \p directory does not exist
     149              :  * \returns mx::error_t::exception if an exception is thrown from the standard library
     150              :  *
     151              :  * \tparam verbose if true then error messages are printed as they occur
     152              :  *
     153              :  *
     154              :  */
     155              : template <class verboseT = verbose::d>
     156              : error_t getFileNames( std::vector<std::string> &fileNames, /** [out] The populated list of file names.*/
     157              :                       const std::string &directory,        /**< [in] The path to the directory to search.
     158              :                                                                      Can not be empty.*/
     159              :                       const std::string &prefix,           /**< [in] The file name prefix (the beginning
     160              :                                                                      characters of the file name) to search
     161              :                                                                      for. If "" then not used.*/
     162              :                       const std::string &substr,           /**< [in] A substring of the filename to search
     163              :                                                                      for. If "" then not used. Only matches
     164              :                                                                      after the first character.*/
     165              :                       const std::string &extension         /**< [in] The file name extension to search for.
     166              :                                                                      If "" then not used. This does not need
     167              :                                                                      to include the ".", as in".ext".*/
     168              : );
     169              : 
     170              : /// Prepend and/or append strings to a file name, leaving the directory and extension unaltered.
     171              : /**
     172              :  * \returns the new file name
     173              :  */
     174              : std::string fileNamePrependAppend( const std::string &fname,   /**< [in] the original file name, possibly including a
     175              :                                                                          directory and extension*/
     176              :                                    const std::string &prepend, /**< [in] is the string to insert at the beginning of the
     177              :                                                                          file name after the path*/
     178              :                                    const std::string &append   /**< [in] is the string to insert at the end of the file
     179              :                                                                          name, before the extension*/
     180              : );
     181              : 
     182              : /// Append a string to a file name, leaving the directory and extension unaltered.
     183              : /**
     184              :  * \returns the new file name
     185              :  */
     186              : std::string fileNameAppend( const std::string &fname, /**< [in] the original file name, possibly including
     187              :                                                                 a directory and extension*/
     188              :                             const std::string &append /**< [in] is the string to insert at the end
     189              :                                                                 of the file name, before the extension*/
     190              : );
     191              : 
     192              : /// Prepend strings to a file name, leaving the directory and extension unaltered.
     193              : /**
     194              :  * \returns the new file name
     195              :  */
     196              : std::string fileNamePrepend( const std::string &fname,  /**< [in] the original file name, possibly including
     197              :                                                                   a directory and extension*/
     198              :                              const std::string &prepend /**< [in] is the string to insert at the beginning of
     199              :                                                                   the file name after the path*/
     200              : );
     201              : 
     202              : /// Get the next file in a numbered sequence
     203              : /** Searches for files in the path designated by basename of the form basenameXXXXextension
     204              :  * where the number of digits in XXXX is set by the \a ndigit parameter.
     205              :  *
     206              :  * \warning this does not currently detect missing files in the sequence, e.g. if you have 0,1,3 in the directory this
     207              :  * will start with 2!
     208              :  *
     209              :  * \todo switch to using a regex or something so we can detect the missing file.
     210              :  *
     211              :  * \retval std::string containing the next filename.
     212              :  *
     213              :  */
     214              : std::string getSequentialFilename( const std::string &basename,       ///< [in] path and initial name of the file*/
     215              :                                    const std::string &extension = "", /**< [in] [optional] extension to append after the
     216              :                                                                                            number. Default is empty.*/
     217              :                                    const int startat = 0,             /**< [in] [optional] number to start the
     218              :                                                                                            search from.
     219              :                                                                                            Default is 0.*/
     220              :                                    int ndigit = 4                     /**< [in] [optional] number of digits in string
     221              :                                                                                            representation
     222              :                                                                                            of the number.Default is 4. */
     223              : );
     224              : 
     225              : /// Get the size in bytes of a file
     226              : /** Uses fstat.
     227              :  *
     228              :  * \returns the file size if fd is valid and no errors occur
     229              :  * \returns -1 on an error
     230              :  */
     231              : off_t fileSize( int fd /**< [in] an open file descriptor */ );
     232              : 
     233              : /// Get the size in bytes of a file pointed to by a FILE pointer
     234              : /** Uses fileno to get the associated descriptor, then uses fstat.
     235              :  *
     236              :  * \returns the file size if fd is valid and no errors occur
     237              :  * \returns -1 on an error
     238              :  *
     239              :  * \overload
     240              :  */
     241              : off_t fileSize( FILE *f /**< [in] an open file */ );
     242              : 
     243              : ///@} -fileutils
     244              : 
     245              : /* ===================================================================== */
     246              : /*                       implementations                                 */
     247              : 
     248              : template <class verboseT>
     249           29 : error_t string2path( std::filesystem::path &path, const std::string &str )
     250              : {
     251              :     try
     252              :     {
     253           29 :         fileUtilsDetail::operationHook()( fileUtilsDetail::operation::stringToPath );
     254           17 :         path = str;
     255              : 
     256           17 :         return error_t::noerror;
     257              :     }
     258           15 :     catch( const std::bad_alloc &e )
     259              :     {
     260              :         // clang-format off
     261              :         #if defined( MXLIB_CATCH_ALL_EXCEPTIONS )
     262            9 :             return internal::mxlib_error_report<verboseT>( error_t::std_bad_alloc, e.what() );
     263              :         #else
     264            6 :             std::throw_with_nested(mx::exception<verboseT>(error_t::std_bad_alloc));
     265              :         #endif
     266              :         // clang-format on
     267              :     }
     268            3 :     catch( const std::filesystem::filesystem_error &e )
     269              :     {
     270              :         // clang-format off
     271              :         #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined(MXLIB_CATCH_NONALLOC_EXCEPTIONS)
     272            3 :             return internal::mxlib_error_report<verboseT>( error_t::std_filesystem_error, e.what() );
     273              :         #else
     274            2 :             std::throw_with_nested(mx::exception<verboseT>(error_t::std_filesystem_error));
     275              :         #endif
     276              :         // clang-format on
     277              :     }
     278            3 :     catch( const std::exception &e )
     279              :     {
     280              :         // clang-format off
     281              :         #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined(MXLIB_CATCH_NONALLOC_EXCEPTIONS)
     282            3 :             return internal::mxlib_error_report<verboseT>( error_t::std_exception, e.what() );
     283              :         #else
     284            2 :             std::throw_with_nested(mx::exception<verboseT>());
     285              :         #endif
     286              :         // clang-format on
     287              :     }
     288            3 :     catch( ... )
     289              :     {
     290              :         // clang-format off
     291              :         #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined(MXLIB_CATCH_NONALLOC_EXCEPTIONS)
     292            3 :             return internal::mxlib_error_report<verboseT>( error_t::exception, "unknown exception");
     293              :         #else
     294            2 :             std::throw_with_nested(mx::exception<verboseT>());
     295              :         #endif
     296              :         // clang-format on
     297              :     }
     298              : }
     299              : 
     300              : template <class verboseT>
     301           14 : bool exists( const std::string &strpath, mx::error_t &errc )
     302              : {
     303           14 :     std::error_code ec;
     304              : 
     305           14 :     std::filesystem::path path;
     306              : 
     307              :     try
     308              :     {
     309           14 :         errc = string2path<verboseT>( path, strpath );
     310              : 
     311           13 :         if( !!errc )
     312              :         {
     313            2 :             internal::mxlib_error_report<verboseT>( errc, "converting path" );
     314            1 :             return false;
     315              :         }
     316              :     }
     317            2 :     catch( const mx::exception<verboseT> &e )
     318              :     {
     319            2 :         std::throw_with_nested( mx::exception<verboseT>( e.code() ) );
     320              :     }
     321              : 
     322           12 :     bool ex = std::filesystem::exists( path, ec );
     323              : 
     324           12 :     if( ec.value() != 0 )
     325              :     {
     326            0 :         errc = mx::errno2error_t( ec.value() );
     327            0 :         if( errc == error_t::error )
     328              :         {
     329            0 :             errc = error_t::filesystem;
     330              :         }
     331              : 
     332            0 :         internal::mxlib_error_report<verboseT>( errc, ec.message() );
     333              : 
     334            0 :         return false;
     335              :     }
     336              : 
     337           12 :     errc = error_t::noerror;
     338           12 :     return ex;
     339           14 : }
     340              : 
     341              : template <class verboseT>
     342            7 : bool dir_exists_is( const std::string &dir, mx::error_t &errc )
     343              : {
     344            7 :     std::error_code ec;
     345              : 
     346            7 :     std::filesystem::path path;
     347              : 
     348              :     try
     349              :     {
     350            7 :         errc = string2path<verboseT>( path, dir );
     351              : 
     352            6 :         if( !!errc )
     353              :         {
     354            2 :             internal::mxlib_error_report<verboseT>( errc, "converting path" );
     355            1 :             return false;
     356              :         }
     357              :     }
     358            2 :     catch( const mx::exception<verboseT> &e )
     359              :     {
     360            2 :         std::throw_with_nested( mx::exception<verboseT>( e.code() ) );
     361              :     }
     362              : 
     363            5 :     bool exists = std::filesystem::exists( path, ec );
     364              : 
     365              :     // clang-format off
     366              :     #ifdef MXLIBTEST_DIREXISTSIS_ISEXISTSERR
     367              :         ec = std::error_code( EEXIST, std::system_category() ); // LCOV_EXCL_LINE
     368              :     #endif
     369              :     // clang-format on
     370              : 
     371            5 :     if( ec.value() != 0 )
     372              :     {
     373            1 :         errc = mx::errno2error_t( ec.value() );
     374            1 :         if( errc == error_t::error )
     375              :         {
     376            0 :             errc = error_t::filesystem;
     377              :         }
     378              : 
     379            1 :         internal::mxlib_error_report<verboseT>( errc, ec.message() );
     380              : 
     381            1 :         return false;
     382              :     }
     383              : 
     384            4 :     if( !exists )
     385              :     {
     386            1 :         return false;
     387              :     }
     388              : 
     389            3 :     bool isdir = std::filesystem::is_directory( path, ec );
     390              : 
     391              :     // clang-format off
     392              :     #ifdef MXLIBTEST_DIREXISTSIS_ISDIRERR
     393              :         ec = std::error_code( EACCES, std::system_category() ); // LCOV_EXCL_LINE
     394              :     #endif
     395              :     // clang-format on
     396              : 
     397            3 :     if( ec.value() != 0 )
     398              :     {
     399            1 :         errc = errno2error_t( ec.value() );
     400            1 :         if( errc == mx::error_t::error )
     401              :         {
     402            0 :             errc = mx::error_t::filesystem;
     403              :         }
     404              : 
     405            1 :         internal::mxlib_error_report<verboseT>( errc, ec.message() );
     406              : 
     407            1 :         return false;
     408              :     }
     409              : 
     410            2 :     errc = error_t::noerror;
     411            2 :     return isdir;
     412            7 : }
     413              : 
     414              : template <class verboseT>
     415           29 : error_t getFileNames( std::vector<std::string> &fileNames,
     416              :                       const std::string &directory,
     417              :                       const std::string &prefix,
     418              :                       const std::string &substr,
     419              :                       const std::string &extension )
     420              : {
     421              :     try // there are several things that can throw here
     422              :     {
     423           29 :         fileUtilsDetail::operationHook()( fileUtilsDetail::operation::getFileNames );
     424           21 :         fileNames.clear();
     425              : 
     426           21 :         if( std::filesystem::exists( directory ) )
     427              :         {
     428           17 :             if( std::filesystem::is_directory( directory ) )
     429              :             {
     430           13 :                 bool hasext = false;
     431           13 :                 std::string _ext;
     432           13 :                 if( extension.size() > 0 )
     433              :                 {
     434            5 :                     if( extension[0] != '.' )
     435              :                     {
     436            1 :                         _ext = '.';
     437              :                     }
     438              : 
     439            5 :                     _ext += extension;
     440              : 
     441            5 :                     hasext = true;
     442              :                 }
     443              : 
     444           13 :                 bool hasprefix = ( prefix.size() > 0 );
     445              : 
     446           13 :                 bool hassub = ( substr.size() > 0 );
     447              : 
     448           13 :                 std::filesystem::directory_iterator it{ directory };
     449           13 :                 auto it_end = std::filesystem::directory_iterator{};
     450          212 :                 for( it; it != it_end; ++it )
     451              :                 {
     452          112 :                     if( hasext )
     453              :                     {
     454           50 :                         if( it->path().extension() != _ext )
     455              :                         {
     456           68 :                             continue;
     457              :                         }
     458              :                     }
     459              : 
     460           87 :                     std::string p = it->path().filename().generic_string();
     461              : 
     462           87 :                     if( hasprefix )
     463              :                     {
     464           41 :                         if( p.size() < prefix.size() )
     465              :                         {
     466            1 :                             continue;
     467              :                         }
     468              :                         else
     469              :                         {
     470              :                             // This won't throw because:
     471              :                             //  - prefix has size > 0
     472              :                             //  - p.size() >= prefix.size()
     473              :                             //  - therefore prefix.size() > 0
     474              :                             //  - so pos1 = 0 will not throw.
     475           40 :                             if( p.compare( 0, prefix.size(), prefix ) != 0 )
     476              :                             {
     477           19 :                                 continue;
     478              :                             }
     479              :                         }
     480              :                     }
     481              : 
     482           67 :                     if( hassub )
     483              :                     {
     484           34 :                         if( p.size() < 2 )
     485              :                         {
     486            1 :                             continue;
     487              :                         }
     488              : 
     489           33 :                         size_t sspos = p.find( substr, 1 ); // only match if not prefix
     490              : 
     491           33 :                         if( sspos == std::string::npos )
     492              :                         {
     493           22 :                             continue;
     494              :                         }
     495              :                     }
     496              : 
     497              :                     // If here then it passed all checks
     498              :                     // this could throw
     499           44 :                     fileNames.push_back( it->path().native() );
     500              :                 }
     501              : 
     502           13 :                 std::sort( fileNames.begin(), fileNames.end() );
     503           13 :             }
     504              :             else
     505              :             {
     506            4 :                 return internal::mxlib_error_report<verboseT>( error_t::invalidarg, directory + " is not a directory" );
     507              :             }
     508              :         }
     509              :         else
     510              :         {
     511            4 :             return internal::mxlib_error_report<verboseT>( error_t::dirnotfound, directory + " was not found" );
     512              :         }
     513              : 
     514           13 :         return error_t::noerror;
     515              :     }
     516            9 :     catch( const std::bad_alloc &e )
     517              :     {
     518              :         // clang-format off
     519              :         #if defined( MXLIB_CATCH_ALL_EXCEPTIONS )
     520            3 :             return internal::mxlib_error_report<verboseT>( error_t::std_bad_alloc, e.what() );;
     521              :         #else
     522            2 :             std::throw_with_nested(mx::exception<verboseT>(error_t::std_bad_alloc));
     523              :         #endif
     524              :         // clang-format on
     525              :     }
     526            3 :     catch( const std::filesystem::filesystem_error &e )
     527              :     {
     528              :         // clang-format off
     529              :         #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined( MXLIB_CATCH_NONALLOC_EXCEPTIONS )
     530            3 :             return internal::mxlib_error_report<verboseT>( error_t::std_filesystem_error, e.what() );
     531              :         #else
     532            2 :             std::throw_with_nested(mx::exception(error_t::std_filesystem_error));
     533              :         #endif
     534              :         // clang-format on
     535              :     }
     536            3 :     catch( const std::exception &e )
     537              :     {
     538              :         // clang-format off
     539              :         #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined( MXLIB_CATCH_NONALLOC_EXCEPTIONS )
     540            3 :             return internal::mxlib_error_report<verboseT>( error_t::std_exception, e.what() );
     541              :         #else
     542            2 :             std::throw_with_nested(mx::exception<verboseT>(error_t::std_exception));
     543              :         #endif
     544              :         // clang-format on
     545              :     }
     546            3 :     catch( ... )
     547              :     {
     548              :         // clang-format off
     549              :         #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined( MXLIB_CATCH_NONALLOC_EXCEPTIONS )
     550            1 :             return internal::mxlib_error_report<verboseT>( error_t::exception );
     551              :         #else
     552            2 :             std::throw_with_nested(mx::exception<verboseT>());
     553              :         #endif
     554              :         // clang-format on
     555              :     }
     556              : }
     557              : 
     558              : #ifdef MXLIBTEST_NAMESPACE
     559              : } // namespace MXLIBTEST_NAMESPACE
     560              : #endif
     561              : 
     562              : } // namespace ioutils
     563              : } // namespace mx
     564              : 
     565              : #endif // fileUtils_hpp
        

Generated by: LCOV version 2.0-1