diff --git a/applications/test/fileName/Test-fileName.C b/applications/test/fileName/Test-fileName.C index de8751c7b01dfa4f5526bd42edf832d9311c70dd..18f89b6db046d049aa071dc6ada3a044cc2cb9d2 100644 --- a/applications/test/fileName/Test-fileName.C +++ b/applications/test/fileName/Test-fileName.C @@ -37,6 +37,7 @@ Description #include "IOstreams.H" #include "OSspecific.H" #include "POSIX.H" +#include "Switch.H" #include "etcFiles.H" using namespace Foam; @@ -47,6 +48,7 @@ using namespace Foam; int main(int argc, char *argv[]) { argList::noParallel(); + argList::addBoolOption("ext", "test handing of file extensions"); argList::addBoolOption("construct", "test constructors"); argList::addBoolOption("default", "reinstate default tests"); argList::addNote("runs default tests or specified ones only"); @@ -108,6 +110,131 @@ int main(int argc, char *argv[]) } + // Test various ext() methods + if (args.optionFound("ext")) + { + Info<<nl << nl << "handling of fileName extension" << nl; + + fileName empty; + fileName endWithDot("some.path/name."); + fileName endWithSlash("some.path/"); + fileName input0("some.file/with.out/extension"); + fileName input1("path.to/media/image.png"); + + Info<<"File : " << input0 << " ext: " + << Switch(input0.hasExt()) + << " = " << input0.ext() << nl; + Info<<"File : " << input1 << " ext: " + << Switch(input1.hasExt()) + << " = " << input1.ext() << nl; + Info<<"File : " << endWithDot << " ext: " + << Switch(endWithDot.hasExt()) + << " = " << endWithDot.ext() << " <-- perhaps return false?" << nl; + Info<<"File : " << endWithSlash << " ext: " + << Switch(endWithSlash.hasExt()) + << " = " << endWithSlash.ext() << nl; + + + Info<<"Remove extension " << (input0.removeExt()); + Info<< " now: " << input0 << nl; + + Info<<"Remove extension " << (input1.removeExt()); + Info<< " now: " << input1 << nl; + + Info<<"Remove extension " << (endWithSlash.removeExt()); + Info<< " now: " << endWithSlash << nl; + + wordList exts{ "jpg", "png", "txt", word::null }; + Info<<"Add extension(s): " << input1 << nl; + for (const word& e : exts) + { + Info<<"<" << e << "> -> " << input1.ext(e) << nl; + } + Info<< nl; + + + Info<<"Test hasExt(word)" << nl + <<"~~~~~~~~~~~~~~~~~" << nl; + Info<<"Has extension(s):" << nl + << "input: " << input1 << nl; + for (const word& e : exts) + { + Info<<" '" << e << "' -> " + << Switch(input1.hasExt(e)) << nl; + } + Info<< nl; + + Info<<"Has extension(s):" << nl + << "input: " << endWithDot << nl; + for (const word& e : exts) + { + Info<<" '" << e << "' -> " + << Switch(endWithDot.hasExt(e)) << nl; + } + Info<< nl; + + + Info<<"Test hasExt(wordRe)" << nl + <<"~~~~~~~~~~~~~~~~~~~" << nl; + + // A regex with a zero length matcher doesn't work at all: + // eg "(png|jpg|txt|)" regex matcher itself + + wordRe matcher0("()", wordRe::REGEXP); + wordRe matcher1("(png|jpg|txt)", wordRe::REGEXP); + wordRe matcher2("(png|txt)", wordRe::REGEXP); + + Info<<"Has extension(s):" << nl + << "input: " << endWithDot << nl; + Info<<" " << matcher0 << " -> " + << Switch(endWithDot.hasExt(matcher0)) << nl; + Info<<" " << matcher1 << " -> " + << Switch(endWithDot.hasExt(matcher1)) << nl; + Info<<" " << matcher2 << " -> " + << Switch(endWithDot.hasExt(matcher2)) << nl; + + Info<< "input: " << input1 << nl; + Info<<" " << matcher0 << " -> " + << Switch(input1.hasExt(matcher0)) << nl; + Info<<" " << matcher1 << " -> " + << Switch(input1.hasExt(matcher1)) << nl; + Info<<" " << matcher2 << " -> " + << Switch(input1.hasExt(matcher2)) << nl; + Info<< nl; + + Info<<"Remove extension(s):" << nl << "input: " << input1 << nl; + while (!input1.empty()) + { + if (input1.removeExt()) + { + Info<< " -> " << input1 << nl; + } + else + { + Info<< "stop> " << input1 << nl; + break; + } + } + Info<< nl; + + input0.clear(); + Info<<"test with zero-sized: " << input0 << nl; + Info<<"add extension: " << input0.ext("abc") << nl; + Info<< nl; + + input0 = "this/"; + Info<<"test add after slash: " << input0 << nl; + Info<<"add extension: " << input0.ext("abc") + << " <-- avoids accidentally creating hidden files" << nl; + Info<< nl; + + input0 = "this.file."; + Info<<"test after dot: " << input0 << nl; + Info<<"add extension: " << input0.ext("abc") + << " <-- No check for repeated dots (user error!)" << nl; + Info<< nl; + } + if (!defaultTests) { return 0; diff --git a/src/OpenFOAM/primitives/strings/fileName/fileName.C b/src/OpenFOAM/primitives/strings/fileName/fileName.C index 7110bdf2f622d515ff960ae403a0c2e3d4c3dda8..70e925ea010e13c9beed06e77470afcc3c925b0a 100644 --- a/src/OpenFOAM/primitives/strings/fileName/fileName.C +++ b/src/OpenFOAM/primitives/strings/fileName/fileName.C @@ -27,6 +27,7 @@ License #include "wordList.H" #include "DynamicList.H" #include "OSspecific.H" +#include "wordRe.H" // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // @@ -303,9 +304,9 @@ Foam::fileName Foam::fileName::path() const Foam::fileName Foam::fileName::lessExt() const { - size_type i = find_last_of("./"); + size_type i = find_ext(); - if (i == npos || i == 0 || operator[](i) == '/') + if (i == npos) { return *this; } @@ -318,9 +319,9 @@ Foam::fileName Foam::fileName::lessExt() const Foam::word Foam::fileName::ext() const { - size_type i = find_last_of("./"); + size_type i = find_ext(); - if (i == npos || i == 0 || operator[](i) == '/') + if (i == npos) { return word::null; } @@ -331,6 +332,71 @@ Foam::word Foam::fileName::ext() const } +Foam::fileName& Foam::fileName::ext(const word& ending) +{ + if (!ending.empty() && !empty() && operator[](size()-1) != '/') + { + append("."); + append(ending); + } + + return *this; +} + + +bool Foam::fileName::hasExt() const +{ + return (find_ext() != npos); +} + + +bool Foam::fileName::hasExt(const word& ending) const +{ + size_type i = find_ext(); + if (i == npos) + { + return false; + } + + ++i; // Do next comparison *after* the dot + return + ( + // Lengths must match + ((size() - i) == ending.size()) + && !compare(i, npos, ending) + ); +} + + +bool Foam::fileName::hasExt(const wordRe& ending) const +{ + size_type i = find_ext(); + if (i == npos) + { + return false; + } + + std::string end = substr(i+1, npos); + return ending.match(end); +} + + +bool Foam::fileName::removeExt() +{ + const size_type i = find_ext(); + + if (i == npos) + { + return false; + } + else + { + this->resize(i); + return true; + } +} + + Foam::wordList Foam::fileName::components(const char delimiter) const { DynamicList<word> wrdList(20); diff --git a/src/OpenFOAM/primitives/strings/fileName/fileName.H b/src/OpenFOAM/primitives/strings/fileName/fileName.H index cc49f365e4f0c6a6234b32a28fa062abdb6f729e..4195077d819587ccd59d73d475fe4c1b19b905fc 100644 --- a/src/OpenFOAM/primitives/strings/fileName/fileName.H +++ b/src/OpenFOAM/primitives/strings/fileName/fileName.H @@ -57,6 +57,7 @@ template<class T> class UList; typedef List<word> wordList; // Forward declaration of friend functions and operators +class wordRe; class fileName; Istream& operator>>(Istream&, fileName&); @@ -73,6 +74,10 @@ class fileName { // Private Member Functions + //- Find position of the file extension dot, return npos on failure. + // A wrapped version of find_last_of("./") with additional logic. + inline size_type find_ext() const; + //- Strip invalid characters inline void stripInvalid(); @@ -104,35 +109,35 @@ public: inline fileName(); //- Construct as copy - inline fileName(const fileName&); + inline fileName(const fileName& fn); //- Construct as copy of word - inline fileName(const word&); + inline fileName(const word& s); //- Construct as copy of string - inline fileName(const string&, const bool doStripInvalid=true); + inline fileName(const string& s, const bool doStripInvalid=true); //- Construct as copy of std::string - inline fileName(const std::string&, const bool doStripInvalid=true); + inline fileName(const std::string& s, const bool doStripInvalid=true); //- Construct as copy of character array - inline fileName(const char*, const bool doStripInvalid=true); + inline fileName(const char* s, const bool doStripInvalid=true); //- Construct by concatenating elements of wordList separated by '/' - explicit fileName(const UList<word>&); + explicit fileName(const UList<word>& lst); //- Construct by concatenating words separated by '/' - explicit fileName(std::initializer_list<word>); + explicit fileName(std::initializer_list<word> lst); //- Construct from Istream - fileName(Istream&); + fileName(Istream& is); // Member functions //- Is this character valid for a fileName? - inline static bool valid(char); + inline static bool valid(char c); //- Cleanup file name // @@ -209,6 +214,24 @@ public: //- Return file name extension (part after last .) word ext() const; + //- Append a '.' and the ending, and return the object. + // The '.' and ending will not be added when the ending is empty, + // or when the file name is empty or ended with a '/'. + fileName& ext(const word& ending); + + //- Return true if it has an extension or simply ends with a '.' + bool hasExt() const; + + //- Return true if the extension is the same as the given ending. + bool hasExt(const word& ending) const; + + //- Return true if the extension matches the given ending. + bool hasExt(const wordRe& ending) const; + + //- Remove extension, returning true if string changed. + bool removeExt(); + + //- Return path components as wordList // // Behaviour: @@ -219,32 +242,46 @@ public: // "foo/bar" 2("foo", "bar") // "/foo/bar" 2("foo", "bar") // "/foo/bar/" 2("foo", "bar") - wordList components(const char delimiter='/') const; + wordList components(const char delimiter = '/') const; //- Return a single component of the path - word component(const size_type, const char delimiter='/') const; + word component + ( + const size_type cmpt, + const char delimiter = '/' + ) const; // Member operators - // Assignment + // Assignment + + //- Copy, no character validation required + void operator=(const fileName& str); + + //- Copy, no character validation required + void operator=(const word& str); + + //- Copy, stripping invalid characters + void operator=(const string& str); + + //- Copy, stripping invalid characters + void operator=(const std::string& str); - void operator=(const fileName&); - void operator=(const word&); - void operator=(const string&); - void operator=(const std::string&); - void operator=(const char*); + //- Copy, stripping invalid characters + void operator=(const char* str); // IOstream operators - friend Istream& operator>>(Istream&, fileName&); - friend Ostream& operator<<(Ostream&, const fileName&); + friend Istream& operator>>(Istream& is, fileName& fn); + friend Ostream& operator<<(Ostream& os, const fileName& fn); }; -//- Assemble words and fileNames as pathnames by adding a '/' separator -fileName operator/(const string&, const string&); +//- Assemble words and fileNames as pathnames by adding a '/' separator. +// No '/' separator is added if either argument is an empty string. +fileName operator/(const string& a, const string& b); //- Recursively search the given directory for the file diff --git a/src/OpenFOAM/primitives/strings/fileName/fileNameI.H b/src/OpenFOAM/primitives/strings/fileName/fileNameI.H index 6c2bb37f9ac16e402f3fee80a27b8c41087fb37a..e2055f74746685b214aa0b9fe605543eebc7015f 100644 --- a/src/OpenFOAM/primitives/strings/fileName/fileNameI.H +++ b/src/OpenFOAM/primitives/strings/fileName/fileNameI.H @@ -25,6 +25,21 @@ License // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // +inline std::string::size_type Foam::fileName::find_ext() const +{ + const size_type i = find_last_of("./"); + + if (i == npos || i == 0 || operator[](i) == '/') + { + return npos; + } + else + { + return i; + } +} + + inline void Foam::fileName::stripInvalid() { // skip stripping unless debug is active to avoid diff --git a/src/OpenFOAM/primitives/strings/fileName/fileNameIO.C b/src/OpenFOAM/primitives/strings/fileName/fileNameIO.C index b87431ca5c96f6cf5bf761d89fdeecf1c67d84e6..dd686bb1314107e4630f6bf1e9f01a022f57580e 100644 --- a/src/OpenFOAM/primitives/strings/fileName/fileNameIO.C +++ b/src/OpenFOAM/primitives/strings/fileName/fileNameIO.C @@ -78,5 +78,3 @@ Foam::Ostream& Foam::operator<<(Ostream& os, const fileName& fn) // ************************************************************************* // - -