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#ifdef MXLIBTEST_NAMESPACE
46namespace 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 */
65template <class verboseT>
66error_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 */
73template <class verboseT = verbose::d>
74bool 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 */
83template <class verboseT = verbose::d>
84bool 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 */
94error_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 */
100std::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 */
106std::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 */
112std::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 */
125template <class verboseT = verbose::d>
126error_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 */
144std::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 */
156std::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 */
166std::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 */
184std::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 */
201off_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 */
211off_t fileSize( FILE *f /**< [in] an open file */ );
212
213///@} -fileutils
214
215/* ===================================================================== */
216/* implementations */
217
218template <class verboseT>
219error_t string2path( std::filesystem::path & path, const std::string &str)
220{
221 try
222 {
223 path = str;
224
225 return error_t::noerror;
226 }
227 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 std::throw_with_nested(mx::exception<verboseT>(error_t::std_bad_alloc));
234 #endif
235 // clang-format on
236 }
237 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 std::throw_with_nested(mx::exception<verboseT>(error_t::std_filesystem_error));
244 #endif
245 // clang-format on
246 }
247 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 std::throw_with_nested(mx::exception<verboseT>());
254 #endif
255 // clang-format on
256 }
257 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 std::throw_with_nested(mx::exception<verboseT>());
264 #endif
265 // clang-format on
266 }
267}
268
269template <class verboseT>
270bool 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
310template <class verboseT>
311bool dir_exists_is( const std::string &dir, mx::error_t &errc )
312{
313 std::error_code ec;
314
315 std::filesystem::path path;
316
317 try
318 {
319 errc = string2path<verboseT>(path, dir);
320
321 if(!!errc)
322 {
323 internal::mxlib_error_report<verboseT>( errc, "converting path" );
324 return false;
325 }
326 }
327 catch(const mx::exception<verboseT> & e)
328 {
329 std::throw_with_nested(mx::exception<verboseT>(e.code()));
330 }
331
332 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 if( ec.value() != 0 )
341 {
342 errc = mx::errno2error_t( ec.value() );
343 if( errc == error_t::error )
344 {
345 errc = error_t::filesystem;
346 }
347
348 internal::mxlib_error_report<verboseT>( errc, ec.message() );
349
350 return false;
351 }
352
353 if( !exists )
354 {
355 return false;
356 }
357
358 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 if( ec.value() != 0 )
367 {
368 errc = errno2error_t( ec.value() );
369 if( errc == mx::error_t::error )
370 {
372 }
373
374 internal::mxlib_error_report<verboseT>( errc, ec.message() );
375
376 return false;
377 }
378
379 errc = error_t::noerror;
380 return isdir;
381}
382
383template <class verboseT>
384error_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 fileNames.clear();
393
394 if( std::filesystem::exists( directory ) )
395 {
396 if( std::filesystem::is_directory( directory ) )
397 {
398 bool hasext = false;
399 std::string _ext;
400 if( extension.size() > 0 )
401 {
402 if( extension[0] != '.' )
403 {
404 _ext = '.';
405 }
406
407 _ext += extension;
408
409 hasext = true;
410 }
411
412 bool hasprefix = ( prefix.size() > 0 );
413
414 bool hassub = ( substr.size() > 0 );
415
416 std::filesystem::directory_iterator it{ directory };
417 auto it_end = std::filesystem::directory_iterator{};
418 for( it; it != it_end; ++it )
419 {
420 if( hasext )
421 {
422 if( it->path().extension() != _ext )
423 {
424 continue;
425 }
426 }
427
428 std::string p = it->path().filename().generic_string();
429
430 if( hasprefix )
431 {
432 if( p.size() < prefix.size() )
433 {
434 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 if( p.compare( 0, prefix.size(), prefix ) != 0 )
444 {
445 continue;
446 }
447 }
448 }
449
450 if( hassub )
451 {
452 if( p.size() < 2 )
453 {
454 continue;
455 }
456
457 size_t sspos = p.find( substr, 1 ); // only match if not prefix
458
459 if( sspos == std::string::npos )
460 {
461 continue;
462 }
463 }
464
465 // If here then it passed all checks
466 // this could throw
467 fileNames.push_back( it->path().native() );
468 }
469
470 std::sort( fileNames.begin(), fileNames.end() );
471 }
472 else
473 {
474 return internal::mxlib_error_report<verboseT>( error_t::invalidarg, directory + " is not a directory" );
475 }
476 }
477 else
478 {
479 return internal::mxlib_error_report<verboseT>( error_t::dirnotfound, directory + " was not found" );
480 }
481
482 return error_t::noerror;
483 }
484 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 std::throw_with_nested(mx::exception<verboseT>(error_t::std_bad_alloc));
491 #endif
492 // clang-format on
493 }
494 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 std::throw_with_nested(mx::exception(error_t::std_filesystem_error));
501 #endif
502 // clang-format on
503 }
504 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 std::throw_with_nested(mx::exception<verboseT>(error_t::std_exception));
511 #endif
512 // clang-format on
513 }
514 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 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
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:2006
@ filesystem
A general filesystem error occurred.
@ error
A general error has occurred.
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:54
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.
Definition fileUtils.cpp:84
std::string pathStem(const std::string &fname)
Get the stem of the filename.
Definition fileUtils.cpp:72
off_t fileSize(int fd)
Get the size in bytes of a file.
std::string pathFilename(const std::string &fname)
Get the base filename.
Definition fileUtils.cpp:78
The mxlib c++ namespace.
Definition mxlib.hpp:37