File indexing completed on 2024-05-19 05:42:02

0001 // ct_lvtclp_fileutil.cpp                                              -*-C++-*-
0002 
0003 /*
0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk>
0005 // SPDX-License-Identifier: Apache-2.0
0006 //
0007 // Licensed under the Apache License, Version 2.0 (the "License");
0008 // you may not use this file except in compliance with the License.
0009 // You may obtain a copy of the License at
0010 //
0011 //     http://www.apache.org/licenses/LICENSE-2.0
0012 //
0013 // Unless required by applicable law or agreed to in writing, software
0014 // distributed under the License is distributed on an "AS IS" BASIS,
0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0016 // See the License for the specific language governing permissions and
0017 // limitations under the License.
0018 */
0019 
0020 #include <ct_lvtclp_fileutil.h>
0021 
0022 #include <algorithm>
0023 
0024 namespace Codethink::lvtclp {
0025 
0026 bool FileUtil::pathStartsWith(const std::filesystem::path& prefix, const std::filesystem::path& path)
0027 {
0028     // Avoid using the path object for mismatch since trailling '/' may not be considered
0029     auto prefixStr = prefix.string();
0030     auto pathStr = path.string();
0031     const auto [it1, _] = std::mismatch(prefixStr.begin(), prefixStr.end(), pathStr.begin(), pathStr.end());
0032     // the whole of prefix matches path (which may be longer)
0033     return it1 == prefixStr.end();
0034 }
0035 
0036 std::filesystem::path FileUtil::nonPrefixPart(const std::filesystem::path& prefix, const std::filesystem::path& path)
0037 // Returns the suffix of path which isn't in the prefix
0038 // e.g. path: /foo/bar/baz, prefix = /foo -> bar/baz
0039 {
0040     const auto [_, it2] = std::mismatch(prefix.begin(), prefix.end(), path.begin(), path.end());
0041 
0042     std::filesystem::path ret;
0043     // return only the part of path after the mismatch: it2 to path.end().
0044     // unfortunately we can't construct a path directly from it2, path.end()
0045     // instead loop through, building up the path
0046     std::for_each(it2, path.end(), [&ret](const auto& elem) {
0047         ret /= elem;
0048     });
0049     return ret;
0050 }
0051 
0052 std::filesystem::path FileUtil::commonParent(const std::vector<std::filesystem::path>& paths)
0053 // All paths should be canonical
0054 {
0055     if (paths.empty()) {
0056         return "";
0057     }
0058 
0059     auto startIt = paths.begin();
0060     const std::filesystem::path& first = *startIt;
0061     ++startIt;
0062 
0063     std::filesystem::path oldPrefix;
0064     std::filesystem::path prefix;
0065 
0066     // for each element in the first path, see if all the paths still start with
0067     // that prefix, returning when they don't all match
0068     // e.g.
0069     // Paths: /home/user/proj/pkgone/pkgone_file.cpp
0070     //        /home/user/proj/pkgtwo/pkgtwo_file.cpp
0071     //
0072     // Loop:
0073     //    - "/" : both match
0074     //    - "/home" : both match
0075     //    - "/home/user" : both match
0076     //    - "/home/user/proj" : both match
0077     //    - "/home/user/proj/pkgone" : second doesn't match
0078     //
0079     // This also takes in consideration if the last patch that matches is
0080     // inside a patch called "groups". This is needed for projects that
0081     // contains a single package, like:
0082     //
0083     // Paths: /home/user/proj/pkggrp/pkgone/pkgone_file.cpp
0084     //        /home/user/proj/pkggrp/pkgone/pkgtwo_file.cpp
0085     // the correct group here is pkggrp and not pkgone.
0086 
0087     for (const auto& component : first) {
0088         prefix /= component;
0089 
0090         auto pathStartsWithPrefix = [&prefix](const std::filesystem::path& path) -> bool {
0091             return pathStartsWith(prefix, path);
0092         };
0093         // start from the second path because the first definately matches
0094         if (!std::all_of(startIt, paths.end(), pathStartsWithPrefix)) {
0095             // now, try to look if the previous patch has *only* one folder.
0096             // and check that this folder is three letters long. this is the
0097             // sign of a project with a single package group.
0098 
0099             if (oldPrefix.filename().string().length() == 3) {
0100                 return oldPrefix.parent_path();
0101             }
0102             return oldPrefix;
0103         }
0104 
0105         oldPrefix = prefix;
0106     }
0107 
0108     return oldPrefix;
0109 }
0110 
0111 } // end namespace Codethink::lvtclp