Skip to content
Snippets Groups Projects
fileName.C 13.3 KiB
Newer Older
/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     |
OpenFOAM bot's avatar
OpenFOAM bot committed
    \\  /    A nd           | www.openfoam.com
     \\/     M anipulation  |
-------------------------------------------------------------------------------
OpenFOAM bot's avatar
OpenFOAM bot committed
    Copyright (C) 2011-2017 OpenFOAM Foundation
    Copyright (C) 2016-2021 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
    This file is part of OpenFOAM.

    OpenFOAM is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    for more details.

    You should have received a copy of the GNU General Public License
    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.

\*---------------------------------------------------------------------------*/

#include "fileName.H"
#include "wordRe.H"
#include "wordList.H"
#include "DynamicList.H"
#include "OSspecific.H"
#include "stringOps.H"

// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //

const char* const Foam::fileName::typeName = "fileName";
int Foam::fileName::debug(Foam::debug::debugSwitch(fileName::typeName, 0));
int Foam::fileName::allowSpaceInFileName
(
    1  // Windows: expect spaces to occur
    Foam::debug::infoSwitch("allowSpaceInFileName", 0)
const Foam::fileName Foam::fileName::null;
// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
namespace
{

// doClean:
//   - remove duplicate slashes, "/./" and "/../" components.
//
// checkValid:
//   - similar to stripInvalid (but silent)
//
// return True if the content changed
static bool cleanFileName
    std::string& str,
    const bool doClean,
    const bool checkValid
    const auto maxLen = str.length();
    std::string::size_type nChar = 0;
    // Preserve UNC \\server\path (windows)
    // - MS-windows only, but handle for other systems
    //   since there is no collision with this pattern
    if (maxLen > 2 && str[0] == '\\' && str[1] == '\\')
    auto top = std::string::npos;  // Not yet found
    bool changed = false;
    for (auto src = nChar; src < maxLen; /*nil*/)
    {
        char c = str[src++];
        // Treat raw backslash like a path separator.
        // There is no "normal" way for these to be there
        // (except for an OS that uses them), but can cause issues
        // when writing strings, shell commands etc.
            str[nChar] = c;
            changed = true;
        }
        else if (checkValid && !Foam::fileName::valid(c))
        {
            // Ignore invalid chars
            // Could explicitly allow space character or rely on
            // allowSpaceInFileName via fileName::valid()
            continue;
        if (c == '/' && top == std::string::npos)
        {
            // Top-level slash not previously determined
            top = (src-1);
        }
            // Repeated '/' - skip it
            if (c == '/')
            // Could be "/./", "/../" or a trailing "/."
            if (c == '.')
            {
                // Trailing "/." - skip it
                if (src >= maxLen)
                {
                    break;
                }

                // Peek at the next character
                const char c1 = str[src];

                // Found "/./" - skip over it
                if (c1 == '/' || c1 == '\\')
                {
                    ++src;
                    continue;
                }

                // Trailing "/.." or intermediate "/../"
                if
                (
                    c1 == '.'
                 &&
                    (
                        src+1 >= maxLen
                     || str[src+1] == '/' || str[src+1] == '\\'
                    )
                )
                {
                    // Backtrack to find the parent directory
                    // Minimum of 3 characters:  '/x/../'
                    // Strip it, provided it is above the top point

                    std::string::size_type parent;
                    if
                    (
                        nChar > 2
                     && top != std::string::npos
                     && (parent = str.rfind('/', nChar-2)) != std::string::npos
                     && parent >= top
                    )
                    {
                        nChar = parent + 1;   // Retain '/' from the parent
                        src += 2;
                        continue;
                    }

                    // Bad resolution, eg 'abc/../../'
                    // Retain the sequence, but move the top to avoid it being
                    // considered a valid parent later
                    top = nChar + 2;
                }
            }
    // Remove trailing '/'
    if (doClean && nChar > 1 && str[nChar-1] == '/')
    str.erase(nChar);
    return changed || (nChar != maxLen);
}

} // End namespace Foam

// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //

bool Foam::fileName::clean(std::string& str)
{
    return cleanFileName(str, true, false);  // clean, checkValid = false
}


Foam::fileName Foam::fileName::validate
(
    const std::string& str,
    const bool doClean
)
{
    fileName out(str, false);           // copy, no stripping
    cleanFileName(out, doClean, true);  // checkValid = true
Foam::fileName Foam::fileName::concat
(
    const std::string& s1,
    const std::string& s2,
    const char delim
)
{
    const auto n1 = s1.length();
    const auto n2 = s2.length();

    fileName out;
    out.reserve(n1 + n2 + 1);

    out += s1;

    if (n1 && n2 && s1.back() != delim && s2.front() != delim)
    }

    out += s2;

    // Could also remove trailing '/', if desired.
    return out;
}


bool Foam::fileName::equals(const std::string& s1, const std::string& s2)
{
    // Do not use (s1 == s2) or s1.compare(s2) first since this would
    // potentially be doing the comparison twice.

    std::string::size_type i1 = 0;
    std::string::size_type i2 = 0;

    const auto n1 = s1.length();
    const auto n2 = s2.length();

    //Info<< "compare " << s1 << " == " << s2 << endl;
    while (i1 < n1 && i2 < n2)
    {
        //Info<< "check '" << s1[i1] << "' vs '" << s2[i2] << "'" << endl;

        if (s1[i1] != s2[i2])
        {
            return false;
        }

        // Increment to next positions and also skip repeated slashes
        do
        {
            ++i1;
        }
        while (s1[i1] == '/');

        do
        {
            ++i2;
        }
        while (s2[i2] == '/');
    }
    //Info<< "return: " << Switch(i1 == n1 && i2 == n2) << endl;

    // Equal if it made it all the way through both strings
    return (i1 == n1 && i2 == n2);
}


bool Foam::fileName::isBackup(const std::string& s)
    if (s.empty())
    else if (s.back() == '~')
    {
        return true;
    }

    // Now check the extension
    auto dot = find_ext(s);

    if (dot == npos)
    {
        return false;
    }

        !s.compare(dot, npos, "bak") || !s.compare(dot, npos, "BAK")
     || !s.compare(dot, npos, "old") || !s.compare(dot, npos, "save")
// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //

Foam::fileName::fileName(const UList<word>& list)
    size_type len = 0;
    for (const word& item : list)
        len += 1 + item.length();   // Include space for '/' needed
    for (const word& item : list)
        if (item.length())
            if (length()) operator+=('/');
Foam::fileName::fileName(std::initializer_list<word> list)
    size_type len = 0;
    for (const word& item : list)
        len += 1 + item.length();   // Include space for '/' needed
    for (const word& item : list)
        if (item.length())
            if (length()) operator+=('/');
    }
}


// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //

Foam::fileName::Type Foam::fileName::type
(
    bool followLink,
    bool checkGzip
) const
    Type t = ::Foam::type(*this, followLink);

    if (checkGzip && (Type::UNDEFINED == t) && size())
    {
        // Also check for gzip file?
        t = ::Foam::type(*this + ".gz", followLink);
    }

    return t;
Foam::fileName& Foam::fileName::toAbsolute()
{
    if (!isAbsolute(*this))
        fileName& f = *this;
        f = cwd()/f;
bool Foam::fileName::clean()
{
    return fileName::clean(*this);
}


std::string Foam::fileName::nameLessExt(const std::string& str)
{
    auto beg = str.rfind('/');
    auto dot = str.rfind('.');
    if (dot != npos && dot <= beg)
    {
        dot = npos;
    }

    return str.substr(beg, dot - beg);
Foam::fileName Foam::fileName::relative
(
    const fileName& parent,
    const bool caseTag
{
    const auto top = parent.size();
    const fileName& f = *this;

    // Everything after "parent/xxx/yyy" -> "xxx/yyy"
    //
    // case-relative:
    //     "parent/xxx/yyy" -> "<case>/xxx/yyy"
        top && (f.size() > (top+1)) && f[top] == '/'
     && f.starts_with(parent)
        {
            return "<case>"/f.substr(top+1);
        }
        else
        {
            return f.substr(top+1);
        }
    }
    else if (caseTag && f.size() && !f.isAbsolute())
    {
        return "<case>"/f;
Foam::wordList Foam::fileName::components(const char delim) const
    const auto parsed = stringOps::split<string>(*this, delim);
    wordList words(parsed.size());
    label i = 0;
    for (const auto& sub : parsed)
        // Could easily filter out '.' here too
        words[i] = sub.str();
        ++i;
    // As a plain wordList
    return words;
}


Foam::word Foam::fileName::component
(
    const size_type cmpt,
    const auto parsed = stringOps::split<string>(*this, delim);
        if (cmpt == std::string::npos)
        {
            return parsed.last().str();
        }
        else if (cmpt < parsed.size())
        {
            return parsed[cmpt].str();
        }
}


// * * * * * * * * * * * * * * * Member Operators  * * * * * * * * * * * * * //

Foam::fileName& Foam::fileName::operator/=(const string& other)
{
    fileName& s = *this;

    if (s.size())
    {
        if (other.size())
        {
            // Two non-empty strings: can concatenate

            if (s.back() != '/' && other.front() != '/')
            {
                s += '/';
            }

        // The first string is empty
// * * * * * * * * * * * * * * * Global Operators  * * * * * * * * * * * * * //
Foam::fileName Foam::operator/(const string& s1, const string& s2)
    if (s1.length())
        if (s2.length())
            // Two non-empty strings: can concatenate
            if (s1.back() == '/' || s2.front() == '/')
                return fileName(s1 + s2);
                return fileName(s1 + '/' + s2);
        // The second string was empty
    if (s2.length())
        // The first string is empty

    // Both strings are empty
    return fileName();
// * * * * * * * * * * * * * * * Global Functions  * * * * * * * * * * * * * //

Foam::fileName Foam::search(const word& file, const fileName& directory)
{
    // Search current directory for the file
    for
    (
        const fileName& item
      : fileHandler().readDir(directory, fileName::FILE)
    )
            return directory/item;
        }
    }

    // If not found search each of the sub-directories
    for
    (
        const fileName& item
      : fileHandler().readDir(directory, fileName::DIRECTORY)
    )
        fileName path = search(file, directory/item);
// ************************************************************************* //