mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
fileUtils.hpp
Go to the documentation of this file.
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
40namespace mx
41{
42namespace ioutils
43{
44
45#ifndef ioutils_fileUtils_detail_hpp
46#define ioutils_fileUtils_detail_hpp
47namespace fileUtilsDetail
48{
49
50/** \cond */
51/// File-utility stages exposed to deterministic exception tests.
52enum class operation
53{
54 stringToPath,
57};
58
59/// Signature of the resettable file-utility operation hook.
60using operationHookT = void ( * )( operation );
61
62/// Access the process-wide file-utility operation hook.
63operationHookT &operationHook();
64
65/// Signature of the resettable file-utility error-code hook.
66using errorCodeHookT = void ( * )( operation, std::error_code & );
67
68/// Access the process-wide file-utility error-code hook.
69errorCodeHookT &errorCodeHook();
70/** \endcond */
71
72} // namespace fileUtilsDetail
73#endif // ioutils_fileUtils_detail_hpp
74
75#ifdef MXLIBTEST_NAMESPACE
76namespace 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 */
95template <class verboseT>
96error_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 */
103template <class verboseT = verbose::d>
104bool 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 */
113template <class verboseT = verbose::d>
114bool 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 */
124error_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 */
130std::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 */
136std::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 */
142std::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 */
155template <class verboseT = verbose::d>
156error_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 */
174std::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 */
186std::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 */
196std::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 */
214std::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 */
231off_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 */
241off_t fileSize( FILE *f /**< [in] an open file */ );
242
243///@} -fileutils
244
245/* ===================================================================== */
246/* implementations */
247
248template <class verboseT>
249error_t string2path( std::filesystem::path &path, const std::string &str )
250{
251 try
252 {
253 fileUtilsDetail::operationHook()( fileUtilsDetail::operation::stringToPath );
254 path = str;
255
256 return error_t::noerror;
257 }
258 catch( const std::bad_alloc &e )
259 {
260 // clang-format off
261 #if defined( MXLIB_CATCH_ALL_EXCEPTIONS )
263 #else
264 std::throw_with_nested(mx::exception<verboseT>(error_t::std_bad_alloc));
265 #endif
266 // clang-format on
267 }
268 catch( const std::filesystem::filesystem_error &e )
269 {
270 // clang-format off
271 #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined(MXLIB_CATCH_NONALLOC_EXCEPTIONS)
273 #else
275 #endif
276 // clang-format on
277 }
278 catch( const std::exception &e )
279 {
280 // clang-format off
281 #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined(MXLIB_CATCH_NONALLOC_EXCEPTIONS)
283 #else
284 std::throw_with_nested(mx::exception<verboseT>());
285 #endif
286 // clang-format on
287 }
288 catch( ... )
289 {
290 // clang-format off
291 #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined(MXLIB_CATCH_NONALLOC_EXCEPTIONS)
293 #else
294 std::throw_with_nested(mx::exception<verboseT>());
295 #endif
296 // clang-format on
297 }
298}
299
300template <class verboseT>
301bool exists( const std::string &strpath, mx::error_t &errc )
302{
303 std::error_code ec;
304
305 std::filesystem::path path;
306
307 try
308 {
309 errc = string2path<verboseT>( path, strpath );
310
311 if( !!errc )
312 {
313 internal::mxlib_error_report<verboseT>( errc, "converting path" );
314 return false;
315 }
316 }
317 catch( const mx::exception<verboseT> &e )
318 {
319 std::throw_with_nested( mx::exception<verboseT>( e.code() ) );
320 }
321
322 bool ex = std::filesystem::exists( path, ec );
323
324 if( ec.value() != 0 )
325 {
326 errc = mx::errno2error_t( ec.value() );
327 if( errc == error_t::error )
328 {
329 errc = error_t::filesystem;
330 }
331
332 internal::mxlib_error_report<verboseT>( errc, ec.message() );
333
334 return false;
335 }
336
337 errc = error_t::noerror;
338 return ex;
339}
340
341template <class verboseT>
342bool dir_exists_is( const std::string &dir, mx::error_t &errc )
343{
344 std::error_code ec;
345
346 std::filesystem::path path;
347
348 try
349 {
350 errc = string2path<verboseT>( path, dir );
351
352 if( !!errc )
353 {
354 internal::mxlib_error_report<verboseT>( errc, "converting path" );
355 return false;
356 }
357 }
358 catch( const mx::exception<verboseT> &e )
359 {
360 std::throw_with_nested( mx::exception<verboseT>( e.code() ) );
361 }
362
363 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 if( ec.value() != 0 )
372 {
373 errc = mx::errno2error_t( ec.value() );
374 if( errc == error_t::error )
375 {
376 errc = error_t::filesystem;
377 }
378
379 internal::mxlib_error_report<verboseT>( errc, ec.message() );
380
381 return false;
382 }
383
384 if( !exists )
385 {
386 return false;
387 }
388
389 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 if( ec.value() != 0 )
398 {
399 errc = errno2error_t( ec.value() );
400 if( errc == mx::error_t::error )
401 {
403 }
404
405 internal::mxlib_error_report<verboseT>( errc, ec.message() );
406
407 return false;
408 }
409
410 errc = error_t::noerror;
411 return isdir;
412}
413
414template <class verboseT>
415error_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 fileUtilsDetail::operationHook()( fileUtilsDetail::operation::getFileNames );
424 fileNames.clear();
425
426 if( std::filesystem::exists( directory ) )
427 {
428 if( std::filesystem::is_directory( directory ) )
429 {
430 bool hasext = false;
431 std::string _ext;
432 if( extension.size() > 0 )
433 {
434 if( extension[0] != '.' )
435 {
436 _ext = '.';
437 }
438
439 _ext += extension;
440
441 hasext = true;
442 }
443
444 bool hasprefix = ( prefix.size() > 0 );
445
446 bool hassub = ( substr.size() > 0 );
447
448 std::filesystem::directory_iterator it{ directory };
449 auto it_end = std::filesystem::directory_iterator{};
450 for( it; it != it_end; ++it )
451 {
452 if( hasext )
453 {
454 if( it->path().extension() != _ext )
455 {
456 continue;
457 }
458 }
459
460 std::string p = it->path().filename().generic_string();
461
462 if( hasprefix )
463 {
464 if( p.size() < prefix.size() )
465 {
466 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 if( p.compare( 0, prefix.size(), prefix ) != 0 )
476 {
477 continue;
478 }
479 }
480 }
481
482 if( hassub )
483 {
484 if( p.size() < 2 )
485 {
486 continue;
487 }
488
489 size_t sspos = p.find( substr, 1 ); // only match if not prefix
490
491 if( sspos == std::string::npos )
492 {
493 continue;
494 }
495 }
496
497 // If here then it passed all checks
498 // this could throw
499 fileNames.push_back( it->path().native() );
500 }
501
502 std::sort( fileNames.begin(), fileNames.end() );
503 }
504 else
505 {
506 return internal::mxlib_error_report<verboseT>( error_t::invalidarg, directory + " is not a directory" );
507 }
508 }
509 else
510 {
511 return internal::mxlib_error_report<verboseT>( error_t::dirnotfound, directory + " was not found" );
512 }
513
514 return error_t::noerror;
515 }
516 catch( const std::bad_alloc &e )
517 {
518 // clang-format off
519 #if defined( MXLIB_CATCH_ALL_EXCEPTIONS )
521 #else
522 std::throw_with_nested(mx::exception<verboseT>(error_t::std_bad_alloc));
523 #endif
524 // clang-format on
525 }
526 catch( const std::filesystem::filesystem_error &e )
527 {
528 // clang-format off
529 #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined( MXLIB_CATCH_NONALLOC_EXCEPTIONS )
531 #else
532 std::throw_with_nested(mx::exception(error_t::std_filesystem_error));
533 #endif
534 // clang-format on
535 }
536 catch( const std::exception &e )
537 {
538 // clang-format off
539 #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined( MXLIB_CATCH_NONALLOC_EXCEPTIONS )
541 #else
542 std::throw_with_nested(mx::exception<verboseT>(error_t::std_exception));
543 #endif
544 // clang-format on
545 }
546 catch( ... )
547 {
548 // clang-format off
549 #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined( MXLIB_CATCH_NONALLOC_EXCEPTIONS )
551 #else
552 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
Augments an exception with the source file and line.
Definition exception.hpp:42
error_t code() const
Get the error code.
error_t
The mxlib error codes.
Definition error_t.hpp:26
static constexpr error_t errno2error_t(const int &err)
Convert an errno code to error_t.
Definition error_t.hpp:2056
@ noerror
No error has occurred.
Definition error_t.hpp:27
@ std_exception
An exception was thrown.
Definition error_t.hpp:52
@ dirnotfound
The directory was not found.
Definition error_t.hpp:46
@ exception
An exception was thrown.
Definition error_t.hpp:51
@ std_bad_alloc
A bad allocation exception was thrown.
Definition error_t.hpp:53
@ filesystem
A general filesystem error occurred.
Definition error_t.hpp:39
@ invalidarg
An argument was invalid.
Definition error_t.hpp:29
@ std_filesystem_error
A filesystem error exception was thrown.
Definition error_t.hpp:61
@ error
A general error has occurred.
Definition error_t.hpp:28
error_t mxlib_error_report(const error_t &code, const std::string &expl, const std::source_location &loc=std::source_location::current())
Print a report to stderr given an mxlib error_t code and explanation and return the code.
Definition error.hpp:331
error_t getFileNames(std::vector< std::string > &fileNames, const std::string &directory, const std::string &prefix, const std::string &substr, const std::string &extension)
Get a list of file names from the specified directory, specifying a prefix, a substring to match,...
std::string fileNamePrepend(const std::string &fname, const std::string &prepend)
Prepend strings to a file name, leaving the directory and extension unaltered.
std::string getSequentialFilename(const std::string &basename, const std::string &extension="", const int startat=0, int ndigit=4)
Get the next file in a numbered sequence.
error_t string2path(std::filesystem::path &path, const std::string &str)
Convert a string to a path, handling exceptions.
std::string fileNamePrependAppend(const std::string &fname, const std::string &prepend, const std::string &append)
Prepend and/or append strings to a file name, leaving the directory and extension unaltered.
std::string fileNameAppend(const std::string &fname, const std::string &append)
Append a string to a file name, leaving the directory and extension unaltered.
bool exists(const std::string &path, mx::error_t &errc)
Check if a path exists.
error_t createDirectories(const std::string &path)
Create a directory or directories.
Definition fileUtils.cpp:83
bool dir_exists_is(const std::string &dir, mx::error_t &errc)
Check if a path exists and is a directory.
std::string parentPath(const std::string &fname)
Get the parent path from a filename.
std::string pathStem(const std::string &fname)
Get the stem of the filename.
off_t fileSize(int fd)
Get the size in bytes of a file.
std::string pathFilename(const std::string &fname)
Get the base filename.
Declarations of some libarary wide utilities.
The mxlib c++ namespace.
Definition mxlib.hpp:37