From a752ea4adabea91f882ce8df904008f954cd9ffd Mon Sep 17 00:00:00 2001 From: David Finkel Date: Thu, 28 Dec 2017 13:29:24 -0500 Subject: [PATCH] Initial commit of a basic path manipulation library. (forked from another project of mine to use C++17 and clean up namespaces a bit.) --- .clang-format | 2 + .gitignore | 4 + .ycm_extra_conf.py | 150 +++++++++++++++++++++ BUILD.bazel | 0 WORKSPACE | 7 + src/BUILD.bazel | 31 +++++ src/path.cc | 330 +++++++++++++++++++++++++++++++++++++++++++++ src/path.h | 77 +++++++++++ src/path_test.cc | 189 ++++++++++++++++++++++++++ src/strings.cc | 22 +++ src/strings.h | 12 ++ 11 files changed, 824 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 .ycm_extra_conf.py create mode 100644 BUILD.bazel create mode 100644 WORKSPACE create mode 100644 src/BUILD.bazel create mode 100644 src/path.cc create mode 100644 src/path.h create mode 100644 src/path_test.cc create mode 100644 src/strings.cc create mode 100644 src/strings.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..d976f67 --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: Google +ColumnLimit: 100 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..73f411b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.swp +.ycm_extra_conf.pyc +bazel-* +compile_commands.json diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py new file mode 100644 index 0000000..c404175 --- /dev/null +++ b/.ycm_extra_conf.py @@ -0,0 +1,150 @@ +# This file is NOT licensed under the GPLv3, which is the license for the rest +# of YouCompleteMe. +# +# Here's the license text for this file: +# +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# For more information, please refer to + +import os +import ycm_core + +# These are the compilation flags that will be used in case there's no +# compilation database set (by default, one is not set). +# CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR. +flags = [ +'-Wall', +'-Wextra', +'-Werror', +# THIS IS IMPORTANT! Without a "-std=" flag, clang won't know which +# language to use when compiling headers. So it will guess. Badly. So C++ +# headers will be compiled as C headers. You don't want that so ALWAYS specify +# a "-std=". +# For a C project, you would set this to something like 'c99' instead of +# 'c++17'. +'-std=c++17', +# ...and the same thing goes for the magic -x option which specifies the +# language that the files to be compiled are written in. This is mostly +# relevant for c++ headers. +# For a C project, you would set this to 'c' instead of 'c++'. +'-x', +'c++', +'-stdlib=libc++', +'-I', +'.', +] + + +# Set this to the absolute path to the folder (NOT the file!) containing the +# compile_commands.json file to use that instead of 'flags'. See here for +# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html +# +# Most projects will NOT need to set this to anything; you can just change the +# 'flags' list of compilation flags. Notice that YCM itself uses that approach. +compilation_database_folder = '' + +if os.path.exists(compilation_database_folder): + database = ycm_core.CompilationDatabase(compilation_database_folder) +else: + database = None + +SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] + +def DirectoryOfThisScript(): + return os.path.dirname(os.path.abspath( __file__ )) + + +def MakeRelativePathsInFlagsAbsolute(flags, working_directory): + if not working_directory: + return list( flags ) + new_flags = [] + make_next_absolute = False + path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] + for flag in flags: + new_flag = flag + + if make_next_absolute: + make_next_absolute = False + if not flag.startswith( '/' ): + new_flag = os.path.join( working_directory, flag ) + + for path_flag in path_flags: + if flag == path_flag: + make_next_absolute = True + break + + if flag.startswith( path_flag ): + path = flag[ len( path_flag ): ] + new_flag = path_flag + os.path.join( working_directory, path ) + break + + if new_flag: + new_flags.append( new_flag ) + return new_flags + + +def IsHeaderFile( filename ): + extension = os.path.splitext( filename )[ 1 ] + return extension in [ '.h', '.hxx', '.hpp', '.hh' ] + + +def GetCompilationInfoForFile( filename ): + # The compilation_commands.json file generated by CMake does not have entries + # for header files. So we do our best by asking the db for flags for a + # corresponding source file, if any. If one exists, the flags for that file + # should be good enough. + if IsHeaderFile( filename ): + basename = os.path.splitext( filename )[ 0 ] + for extension in SOURCE_EXTENSIONS: + replacement_file = basename + extension + if os.path.exists( replacement_file ): + compilation_info = database.GetCompilationInfoForFile( + replacement_file ) + if compilation_info.compiler_flags_: + return compilation_info + return None + return database.GetCompilationInfoForFile( filename ) + + +def FlagsForFile(filename, **kwargs): + if database: + # Bear in mind that compilation_info.compiler_flags_ does NOT return a + # python list, but a "list-like" StringVec object + compilation_info = GetCompilationInfoForFile(filename) + if not compilation_info: + return None + + final_flags = MakeRelativePathsInFlagsAbsolute( + compilation_info.compiler_flags_, + compilation_info.compiler_working_dir_ ) + + else: + relative_to = DirectoryOfThisScript() + final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) + + return { + 'flags': final_flags, + 'do_cache': True + } diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..e69de29 diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..df13674 --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,7 @@ +workspace(name = "spin_2_path") + +git_repository( + name = "com_google_googletest", + commit = "4d1f930af878dbd929f33088c375175896f4e365", + remote = "https://github.com/google/googletest.git", +) diff --git a/src/BUILD.bazel b/src/BUILD.bazel new file mode 100644 index 0000000..5faa79c --- /dev/null +++ b/src/BUILD.bazel @@ -0,0 +1,31 @@ +cc_library( + name = "strings", + srcs = ["strings.cc"], + hdrs = ["strings.h"], + copts = ["-std=c++17"], +) + + +cc_library( + name = "path", + srcs = ["path.cc"], + hdrs = ["path.h"], + copts = ["-std=c++17"], + deps = [":strings"], +) + + +cc_test( + name = "path_test", + srcs = ["path_test.cc"], + copts = [ + "-Iexternal/gtest/googlemock/include", + "-Iexternal/gtest/googletest/include", + "-std=c++17", + ], + deps = [ + ":path", + "@gtest//:gmock_main", + ], +) + diff --git a/src/path.cc b/src/path.cc new file mode 100644 index 0000000..86124b0 --- /dev/null +++ b/src/path.cc @@ -0,0 +1,330 @@ +#include "path.h" +#include "strings.h" + +#include + +namespace spin_2_fs { +using namespace std::string_literals; + +namespace { + +bool IsAbsolute(const std::string &path) { + if (path.empty()) { + return false; + } + if (path.front() == '/') { + return true; + } + return false; +} + +bool IsDirectory(const std::string &path) { + if (path.empty()) { + return false; + } + if (path.back() == '/') { + return true; + } + return false; +} + +void StripEmptyPrefixes(std::list *components, + const bool absolute) { + // remove the empty prefix in case the "absolute" path starts with two or more + // slashes, a few "." components or ".." components (the last only for + // absolute paths). + while (!components->empty() && + (components->front().empty() || components->front() == "." || + (absolute && components->front() == ".."))) { + components->erase(components->begin()); + } +} +std::list CanonicalizePathList(std::list components, + bool absolute) { + StripEmptyPrefixes(&components, absolute); + // Keep track of how far we are into a prefix of non-".." components so far, + // to handle a "../.." prefix correctly in relative paths. + // set the non-parent prefix depth to 2^40 (~10^12) if it's an absolute path + // so we never mode-switch to preserving the ".." components. (I'll be + // impressed the day someone has enough RAM to construct a 2TiB path string, + // let alone constructs one and passes it into this function..) + int64_t non_parent_prefix_depth = absolute ? 1l << 40 : 0; + for (auto it = components.begin(); it != components.end(); it++) { + if (!(it->empty() || *it == "." || *it == "..")) { + non_parent_prefix_depth++; + } + if (*it == "." || it->empty()) { + if (it != components.begin()) { + components.erase(it--); + } else { + StripEmptyPrefixes(&components, absolute); + it = components.begin(); + } + } + if (*it == ".." && (non_parent_prefix_depth > 0)) { + auto next_component = it; + next_component++; + if (it != components.begin()) { + auto prev_dir = --it; + --it; + components.erase(prev_dir, next_component); + non_parent_prefix_depth--; + } else { + StripEmptyPrefixes(&components, absolute); + it = components.begin(); + non_parent_prefix_depth = absolute ? 1 << 20 : 0; + } + } + } + + return components; +} + +std::list CanonicalizePathList(const std::string &path) { + std::list components = SplitStrings(path, '/'); + const bool absolute = IsAbsolute(path); + return CanonicalizePathList(components, absolute); +} + +} // anonymous namespace + +Path::Path(const std::string &path) + : components_( + std::make_shared>(CanonicalizePathList(path))), + absolute_(IsAbsolute(path)), directory_(IsDirectory(path)), + num_components_(components_->size()) {} + +Path::Path(std::list path, bool abs, bool dir) + : components_(std::make_shared>(std::move(path))), + absolute_(abs), directory_(dir), num_components_(components_->size()) {} + +Path::Path(std::shared_ptr> path, bool abs, + bool dir, int64_t num_components) + : components_(path), absolute_(abs), directory_(dir), + num_components_(num_components) {} + +std::string Path::to_string() const { + std::string canonical_path; + int64_t done_dirs = 0; + if (num_components_ <= 0 || components_->empty()) { + if (absolute_) { + return "/"s; + } else { + return "."s; + } + } + for (const auto &c : *components_) { + canonical_path += "/"s + c; + if (++done_dirs >= num_components_) { + break; + } + } + if (!absolute_) { + canonical_path.erase(0, 1); + } + if (directory_) { + canonical_path += "/"s; + } + + return canonical_path; +} + +std::list Path::get_list() const { + std::list components = *components_; + components.resize(num_components_); + return components; +} + +Path Path::parent() const { + std::shared_ptr> components = components_; + int64_t new_components = std::max(num_components_ - 1, 0); + // special handling for the relative case where we hit the beginning. + if (!absolute_) { + bool all_parents = true; + int64_t scanned_dirs = 0; + // If there are still non-".." parents, we can continue reducing + // num_components_ + for (const auto &dir : *components_) { + // don't try to consume beyond num_components_. + if (scanned_dirs++ >= num_components_) { + break; + } + if (dir != "..") { + all_parents = false; + break; + } + } + // this includes the empty case. + if (all_parents) { + std::list components_l = *components_; + components_l.push_front(".."); + components = std::make_shared>( + std::move(components_l)); + new_components = num_components_ + 1; + } + } + Path up(std::move(components), absolute_, /* dir = */ true, + /* num_components = */ new_components); + return up; +} + +bool Path::is_root() const { return num_components_ == 0 && absolute_; } + +bool Path::operator<(const Path &other) const { + // make relative paths sort after absolute ones. + if (other.absolute_ != absolute_) { + if (other.absolute_ && !absolute_) { + return false; + } + return true; + } + if (components_ == other.components_) { + // it's the same list, compare lengths. + if (num_components_ != other.num_components_) { + return (num_components_ < other.num_components_); + } + if (directory_ != other.directory_) { + return !directory_; + } + } + auto it = components_->begin(); + auto oit = other.components_->begin(); + + for (int i = 0; i < num_components_ && i < other.num_components_ && + it != components_->end() && oit != other.components_->end(); + i++, it++, oit++) { + if (*it == *oit) { + continue; + } + if (*it < *oit) { + return true; + } + return false; + } + + // we hit the end of something, so we'll have to disambiguate by lengths. + return num_components_ < other.num_components_; +} + +bool Path::has_parent(const Path &path) const { + if (absolute_ != path.absolute_) { + return false; + } + if (path.components_ == components_) { + return path.num_components_ < num_components_; + } + auto it = components_->begin(); + auto oit = path.components_->begin(); + + for (int i = 0; i < num_components_ && i < path.num_components_ && + it != components_->end() && oit != path.components_->end(); + i++, it++, oit++) { + if (*it == *oit) { + continue; + } + return false; + } + return path.num_components_ < num_components_; +} + +Path Path::Join(const Path &suffix) const { + std::list new_components = get_list(); + new_components.splice(new_components.end(), suffix.get_list()); + + return Path(CanonicalizePathList(new_components, absolute_), absolute_, + suffix.directory_); +} + +Path Path::Cwd() { + std::string cwd; + cwd.assign(get_current_dir_name()); + + return Path(CanonicalizePathList(cwd), true, true); +} + +Path Path::absolute() const { + if (absolute_) { + return *this; + } + return Cwd() / *this; +} + +bool Path::operator==(const Path &other) const { + if (absolute_ != other.absolute_) { + return false; + } + if (directory_ != other.directory_) { + return false; + } + if (num_components_ != other.num_components_) { + return false; + } + if (components_ == other.components_) { + return true; + } + auto it = components_->begin(); + auto oit = components_->begin(); + for (int i = 0; i < num_components_ && it != components_->end(); + i++, it++, oit++) { + if (*it == *oit) { + continue; + } + return false; + } + return true; +} + +std::experimental::optional +Path::make_relative(const fs::Path &parent) const { + if (!has_parent(parent)) { + // This is not a parent, so there isn't anything we can do, just return + // null. + return std::experimental::nullopt; + } + if (absolute_ != parent.absolute_) { + return std::experimental::nullopt; + } + auto first_preserved = components_->begin(); + std::advance(first_preserved, parent.num_components_); + std::list new_components(first_preserved, components_->end()); + return Path(std::move(new_components), false, directory_); +} + +std::string Path::last_component() const { + if (num_components_ == 0) { + return std::string(); + } + return components_->back(); +} + +Path Path::common_parent(const Path &other) const { + if (is_absolute() != other.is_absolute()) { + // if one is absolute and the other isn't, just return the root. + return Path::Root(); + } + if (is_absolute()) { + if (components_ == other.components_) { + // The backing list of components is represented by the same shared_ptr, + // so we are guaranteed that these differ only in the directory attribute + // and/or num_components, making the with the smaller num_components_ the + // parent (if it's a directory). (otherwise, it's its parent) + const Path shorter = + num_components_ < other.num_components_ ? *this : other; + // This conditinoal is particlularly important if &other == this. + return shorter.directory_ ? shorter : shorter.parent(); + } + Path cur_parent = num_components_ < other.num_components_ ? *this : other; + while (!cur_parent.is_root()) { + if (other.has_parent(cur_parent)) { + return cur_parent; + } + cur_parent = cur_parent.parent(); + } + return Path::Root(); + } + // These are both relative paths. + // We hit the boundary condition. Return an empty relative path. + return Path(std::list(), /* abs = */ false, /* dir = */ true); +} + +} // namespace spin_2_fs diff --git a/src/path.h b/src/path.h new file mode 100644 index 0000000..8bf622c --- /dev/null +++ b/src/path.h @@ -0,0 +1,77 @@ +#ifndef include_path_h +#define include_path_h + +#include + +#include +#include +#include +#include + +namespace spin_2_fs { + +class Path { +public: + explicit Path(const std::string &path); + // Enable the default move and copy constructors. + Path(const Path &path) = default; + Path(Path &&path) = default; + Path &operator=(const Path &) = default; + Path &operator=(Path &&) = default; + + Path(std::list path, bool abs, bool dir); + + static Path Cwd(); + inline static Path Root() { + return Path(std::list{}, true, true); + } + + std::string to_string() const; + + // returns the parent directory. + Path parent() const; + // returns true if the argument is a parent of *this (e.g. "/" is always a + // parent of an absolute path) + bool has_parent(const Path &path) const; + Path common_parent(const Path &other) const; + + bool is_root() const; + + inline bool is_absolute() const { return absolute_; } + + bool operator<(const Path &other) const; + + Path Join(const Path &suffix) const; + inline Path operator/(const Path &suffix) const { return Join(suffix); } + bool operator==(const Path &other) const; + inline bool operator!=(const Path &other) const { return !(*this == other); } + // Return a new, relative path constructed by removing a parent. + std::optional make_relative(const Path &parent) const; + + Path absolute() const; + + std::string last_component() const; + +private: + std::list get_list() const; + + Path(std::shared_ptr> path, bool abs, bool dir, + int64_t num_components); + std::shared_ptr> components_; + // True if this was an absolute path. + bool absolute_; + // True if there was a trailing slash. + bool directory_; + // Number of directories in the path, this allows the use of refcounting when + // constructing a parent. + int64_t num_components_; +}; + +// Define an ostream operator so gtest knows how to pretty-print Paths. +inline std::ostream &operator<<(std::ostream &stream, Path path) { + stream << path.to_string(); + return stream; +} + +} // namespace spin_2_fs +#endif // include_path_h diff --git a/src/path_test.cc b/src/path_test.cc new file mode 100644 index 0000000..7b9f8f3 --- /dev/null +++ b/src/path_test.cc @@ -0,0 +1,189 @@ +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "path.h" + +namespace spin_2_fs { +namespace { + +TEST(TestPath, CanonicalizeAbsolutePaths) { + EXPECT_EQ("/foo/bar/fim", Path("/foo/bar/fim").to_string()); + EXPECT_EQ("/foo/bar/fim", Path("/foo/bar/./fim").to_string()); + EXPECT_EQ("/foo/bar/fim", Path("/foo/bar/boo/../fim").to_string()); + EXPECT_EQ("/foo/bar/fim", Path("/foo/bar/boo/bim/../../fim").to_string()); + EXPECT_EQ("/fim", Path("/foo/bar/boo/bim/../../../../../fim").to_string()); + EXPECT_EQ("/fim", + Path("/foo/bar/boo/bim/../../../../../../../fim").to_string()); + EXPECT_EQ("/fim", + Path("/foo/bar/boo/bim/.././.././../../../fim").to_string()); + EXPECT_EQ("/foo/fim", Path("/foo/./boo/../fim").to_string()); + EXPECT_EQ("/", Path("/.././.././../////.././").to_string()); + EXPECT_EQ("/foo/fim", Path("/foo/bar/boo/bim/../../../fim").to_string()); + EXPECT_EQ("/fim", Path("/foo/bar/boo/bim/../../../.././fim").to_string()); + EXPECT_EQ("/foo/bar/fim", Path("/foo/bar/boo/bim/../.././fim").to_string()); +} + +TEST(TestPath, ConstructParentAbsolute) { + Path fim("/foo/bar/fim"); + EXPECT_EQ("/foo/bar/", fim.parent().to_string()); + EXPECT_EQ("/foo/", fim.parent().parent().to_string()); + EXPECT_EQ("/", fim.parent().parent().parent().to_string()); + // Verify that continuing to request the parent after hitting the root + // returns the root. + EXPECT_EQ("/", fim.parent().parent().parent().parent().to_string()); +} +TEST(TestPath, ConstructParentRelative) { + Path fim("foo/bar/fim"); + EXPECT_EQ("foo/bar/", fim.parent().to_string()); + EXPECT_EQ("foo/", fim.parent().parent().to_string()); + EXPECT_EQ(".", fim.parent().parent().parent().to_string()); + // Verify that we start getting ".." components as we add more parents. + EXPECT_EQ("../", fim.parent().parent().parent().parent().to_string()); + EXPECT_EQ("../../", + fim.parent().parent().parent().parent().parent().to_string()); + EXPECT_EQ("../../../", Path("").parent().parent().parent().to_string()); + EXPECT_EQ("../../../../", Path("..").parent().parent().parent().to_string()); + EXPECT_EQ(".", Path("").to_string()); + EXPECT_EQ(".", Path(".").to_string()); + EXPECT_EQ("../", Path("").parent().to_string()); + EXPECT_EQ("../", Path(".").parent().to_string()); +} + +TEST(TestPath, IsRoot) { + Path fim("/foo/bar/fim"); + EXPECT_TRUE(fim.parent().parent().parent().parent().is_root()); + EXPECT_TRUE(fim.parent().parent().parent().is_root()); + EXPECT_FALSE(fim.parent().parent().is_root()); + EXPECT_FALSE(fim.parent().is_root()); + EXPECT_FALSE(fim.is_root()); +} + +TEST(TestPath, IsAbsolute) { + Path f("foo/bar/bim"); + EXPECT_EQ("foo/bar/bim", f.to_string()); + EXPECT_FALSE(f.is_absolute()); + Path g("/foo/bar/bim"); + EXPECT_TRUE(g.is_absolute()); +} + +TEST(TestPath, CanonicalizeRelativePath) { + Path foo("../foo/bar"); + EXPECT_EQ("../foo/bar", foo.to_string()); + EXPECT_EQ("../foo/", foo.parent().to_string()); + EXPECT_EQ("../foo/bar", Path("./.././foo/./fim/../bar").to_string()); + EXPECT_EQ("../foo/bar/", Path("./.././foo/./fim/../bar/").to_string()); + EXPECT_EQ("../foo/fim/bim/bar/", + Path("./.././foo/fim/bim/bop/../bar/").to_string()); + EXPECT_EQ("../../foo/fim/bim/bar/", + Path("../../foo/fim/bim/bar/").to_string()); + EXPECT_EQ("../../../../../../foo/fim/bim/bar/", + Path("../../../../../../foo/fim/bim/bar/").to_string()); + EXPECT_EQ("../../../../foo/fim/bim/bar/", + Path("../../foo/bar/../../../../foo/fim/bim/bar/").to_string()); +} + +TEST(TestPath, IsParent) { + Path bar("/foo/bar/"); + Path foo("/foo/"); + EXPECT_TRUE(bar.has_parent(foo)); + EXPECT_FALSE(foo.has_parent(bar)); + Path bim("/foo/bar/bop/bim"); + EXPECT_TRUE(bim.has_parent(foo)); + Path root("/"); + EXPECT_TRUE(bim.has_parent(root)); + Path bar_foo("/foo/bar/bim/boo/bock"); + Path bar_from_parent = bar_foo.parent().parent().parent(); + EXPECT_EQ(bar_from_parent.to_string(), bar.to_string()); + EXPECT_EQ(bar_from_parent, bar); + EXPECT_TRUE(bim.has_parent(bar_from_parent)); +} + +TEST(TestPath, TestJoin) { + const Path bar("foo/bar"); + const Path bin("/boo/bin/bim"); + Path bar_abs = bin.Join(bar); + EXPECT_EQ("/boo/bin/bim/foo/bar", bar_abs.to_string()); + Path bar_abs_slash = bin / bar; + EXPECT_EQ("/boo/bin/bim/foo/bar", bar_abs_slash.to_string()); + + EXPECT_EQ("/foo/bar/bim/boo", + (Path("/foo/bar/bim/bing") / Path("../boo")).to_string()); + EXPECT_EQ("/foo/bar/boo", + (Path("/foo/bar/bim/bing") / Path("../../boo")).to_string()); + EXPECT_EQ("/foo/bar/boo", + (Path("/foo/bar/bim/bing") / Path("./../../boo")).to_string()); + EXPECT_EQ("/foo/bar/boo/", + (Path("/foo/bar/bim/bing") / Path("./../../boo/")).to_string()); + + Path vic("voo/vim/vik"); + { + Path von("../von"); + EXPECT_EQ("voo/vim/von", (vic / von).to_string()); + } + { + Path von("../../vim/von"); + EXPECT_EQ("voo/vim/von", (vic / von).to_string()); + } + EXPECT_EQ("voo/vim/von", (vic / Path("./../../vim/von")).to_string()); + EXPECT_EQ("voo/vim/von", (vic / Path("../.././vim/von")).to_string()); +} + +TEST(TestPath, TestCwd) { + const Path bar = Path::Cwd(); + EXPECT_TRUE(bar.is_absolute()); + ASSERT_TRUE(chdir("/tmp") == 0); + const Path tmp = Path::Cwd(); + EXPECT_EQ("/tmp/", tmp.to_string()); +} + +TEST(TestPath, TestAbsolute) { + const Path bar = Path::Cwd(); + EXPECT_TRUE(bar.is_absolute()); + ASSERT_TRUE(chdir("/tmp") == 0); + const Path tmp = Path::Cwd(); + const Path i("foo/bar/bin"); + EXPECT_EQ("foo/bar/bin", i.to_string()); + EXPECT_EQ("/tmp/foo/bar/bin", i.absolute().to_string()); + EXPECT_TRUE(i.absolute().is_absolute()); +} + +TEST(TestPath, TestMakeRelative) { + const Path foo("/bim/bar/foo"); + const Path bim("/bim"); + const std::optional bar_foo = foo.make_relative(bim); + EXPECT_EQ(bar_foo.value(), Path("bar/foo")); + const std::optional dne = bim.make_relative(foo); + EXPECT_EQ(dne.value_or(Path("/foo/bar/bim/boo/bop/vim")), + Path("/foo/bar/bim/boo/bop/vim")); +} + +TEST(TestPath, TestRoot) { + const Path root = Path::Root(); + EXPECT_EQ(root, Path::Root()); + EXPECT_EQ(root, Path("/")); +} + +TEST(TestPath, TestLastComponent) { + const Path foo("/bim/bar/foo"); + const Path bim("/bim"); + const std::string bar_foo = foo.last_component(); + EXPECT_EQ(bar_foo, "foo"); + const std::string dne = bim.last_component(); + EXPECT_EQ(dne, "bim"); +} + +TEST(TestPath, TestCommonParent) { + const Path foo("/bar/baz/bim/foo/"); + const Path baz = foo.parent().parent(); + EXPECT_EQ(foo.common_parent(baz), baz); + EXPECT_EQ(baz.common_parent(foo), baz); + // foo is a directory, so it is its own common parent. + EXPECT_EQ(foo.common_parent(foo), foo); + // baz is a directory, so it is its own common parent. + EXPECT_EQ(baz.common_parent(baz), baz); + const Path baz_indep("/bar/baz/"); + EXPECT_EQ(foo.common_parent(baz_indep), baz); + EXPECT_EQ(baz_indep.common_parent(foo), baz); +} +} // anonymous namespace +} // namespace spin_2_fs diff --git a/src/strings.cc b/src/strings.cc new file mode 100644 index 0000000..116aded --- /dev/null +++ b/src/strings.cc @@ -0,0 +1,22 @@ +#include "strings.h" + +namespace spin_2_fs { + +std::list SplitStrings(const std::string &in, char sep) { + std::list components; + auto sep_iter = in.begin(); + for (auto it = in.begin(); it != in.end(); it++) { + if (*it == sep) { + components.push_back({(sep_iter), it}); + sep_iter = it + 1; + } + } + if ((sep_iter) != in.end()) { + components.push_back({(sep_iter), in.end()}); + } else { + components.push_back(std::string()); + } + return components; +} + +} // namespace spin_2_fs diff --git a/src/strings.h b/src/strings.h new file mode 100644 index 0000000..bb5fc7c --- /dev/null +++ b/src/strings.h @@ -0,0 +1,12 @@ +#ifndef include_strings_h +#define include_strings_h + +#include +#include + +namespace spin_2_fs { +std::list SplitStrings(const std::string &in, char sep); + +} // namespace spin_2_fs + +#endif // include_strings_h