LCOV - code coverage report
Current view: top level - ioutils - fileUtils.hpp (source / functions) Coverage Total Hit
Test: mxlib Lines: 71.4 % 84 60
Test Date: 2026-02-19 16:58:26 Functions: 100.0 % 3 3

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

Generated by: LCOV version 2.0-1