diff options
author | Brad King <brad.king@kitware.com> | 2023-04-06 13:16:22 (GMT) |
---|---|---|
committer | Kitware Robot <kwrobot@kitware.com> | 2023-04-06 13:16:31 (GMT) |
commit | 0dcb5456029e98c41153bcf4377cd406781c4df4 (patch) | |
tree | 7bced4cb1021ca5721eb5eb0ce1c7d7d569100a3 | |
parent | fbdb509efe21a02f075536eaf7fde0594e9419ef (diff) | |
parent | 72d116ee68efc1532a66b67730c1731c10d38f09 (diff) | |
download | CMake-0dcb5456029e98c41153bcf4377cd406781c4df4.zip CMake-0dcb5456029e98c41153bcf4377cd406781c4df4.tar.gz CMake-0dcb5456029e98c41153bcf4377cd406781c4df4.tar.bz2 |
Merge topic 'cmList-class'
72d116ee68 GenEx: list oriented genexes use cmList class
9f60f19ee9 cmList: CMake list implementation
Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !8375
-rw-r--r-- | Source/CMakeLists.txt | 2 | ||||
-rw-r--r-- | Source/cmAlgorithms.h | 2 | ||||
-rw-r--r-- | Source/cmGeneratorExpressionNode.cxx | 45 | ||||
-rw-r--r-- | Source/cmList.cxx | 996 | ||||
-rw-r--r-- | Source/cmList.h | 1198 | ||||
-rw-r--r-- | Tests/CMakeLib/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Tests/CMakeLib/testList.cxx | 995 | ||||
-rwxr-xr-x | bootstrap | 1 |
8 files changed, 3211 insertions, 29 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 3ae0bc6..b691d38 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -333,6 +333,8 @@ add_library( cmLinkLineComputer.h cmLinkLineDeviceComputer.cxx cmLinkLineDeviceComputer.h + cmList.h + cmList.cxx cmListFileCache.cxx cmListFileCache.h cmLocalCommonGenerator.cxx diff --git a/Source/cmAlgorithms.h b/Source/cmAlgorithms.h index a1830f9..2c3ee9b 100644 --- a/Source/cmAlgorithms.h +++ b/Source/cmAlgorithms.h @@ -95,7 +95,7 @@ typename Range::const_iterator cmRemoveIndices(Range& r, InputRange const& rem) } template <typename Range, typename MatchRange> -typename Range::const_iterator cmRemoveMatching(Range& r, MatchRange const& m) +auto cmRemoveMatching(Range& r, MatchRange const& m) -> decltype(r.begin()) { return std::remove_if(r.begin(), r.end(), ContainerAlgorithms::BinarySearcher<MatchRange>(m)); diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx index 6e78f55..a47366b 100644 --- a/Source/cmGeneratorExpressionNode.cxx +++ b/Source/cmGeneratorExpressionNode.cxx @@ -12,6 +12,7 @@ #include <memory> #include <set> #include <sstream> +#include <stdexcept> #include <unordered_map> #include <utility> @@ -25,7 +26,6 @@ #include "cmsys/RegularExpression.hxx" #include "cmsys/String.h" -#include "cmAlgorithms.h" #include "cmCMakePath.h" #include "cmComputeLinkInformation.h" #include "cmGeneratorExpression.h" @@ -35,6 +35,7 @@ #include "cmGeneratorTarget.h" #include "cmGlobalGenerator.h" #include "cmLinkItem.h" +#include "cmList.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" #include "cmMessageType.h" @@ -291,18 +292,18 @@ static const struct InListNode : public cmGeneratorExpressionNode const GeneratorExpressionContent* /*content*/, cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override { - std::vector<std::string> values; - std::vector<std::string> checkValues; + cmList values; + cmList checkValues; bool check = false; switch (context->LG->GetPolicyStatus(cmPolicies::CMP0085)) { case cmPolicies::WARN: if (parameters.front().empty()) { check = true; - cmExpandList(parameters[1], checkValues, true); + checkValues.assign(parameters[1], cmList::EmptyElements::Yes); } CM_FALLTHROUGH; case cmPolicies::OLD: - cmExpandList(parameters[1], values); + values.assign(parameters[1]); if (check && values != checkValues) { std::ostringstream e; e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0085) @@ -319,11 +320,11 @@ static const struct InListNode : public cmGeneratorExpressionNode case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: case cmPolicies::NEW: - cmExpandList(parameters[1], values, true); + values.assign(parameters[1], cmList::EmptyElements::Yes); break; } - return cm::contains(values, parameters.front()) ? "1" : "0"; + return values.find(parameters.front()) != cmList::npos ? "1" : "0"; } } inListNode; @@ -352,24 +353,17 @@ static const struct FilterNode : public cmGeneratorExpressionNode return {}; } - const bool exclude = parameters[1] == "EXCLUDE"; - - cmsys::RegularExpression re; - if (!re.compile(parameters[2])) { + try { + return cmList{ parameters.front(), cmList::EmptyElements::Yes } + .filter(parameters[2], + parameters[1] == "EXCLUDE" ? cmList::FilterMode::EXCLUDE + : cmList::FilterMode::INCLUDE) + .to_string(); + } catch (std::invalid_argument&) { reportError(context, content->GetOriginalExpression(), "$<FILTER:...> failed to compile regex"); return {}; } - - std::vector<std::string> values; - std::vector<std::string> result; - cmExpandList(parameters.front(), values, true); - - std::copy_if(values.cbegin(), values.cend(), std::back_inserter(result), - [&re, exclude](std::string const& input) { - return exclude ^ re.find(input); - }); - return cmJoin(cmMakeRange(result.cbegin(), result.cend()), ";"); } } filterNode; @@ -391,11 +385,7 @@ static const struct RemoveDuplicatesNode : public cmGeneratorExpressionNode "$<REMOVE_DUPLICATES:...> expression requires one parameter"); } - std::vector<std::string> values = cmExpandedList(parameters.front(), true); - - auto valuesEnd = cmRemoveDuplicates(values); - auto valuesBegin = values.cbegin(); - return cmJoin(cmMakeRange(valuesBegin, valuesEnd), ";"); + return cmList{ parameters.front() }.remove_duplicates().to_string(); } } removeDuplicatesNode; @@ -1500,8 +1490,7 @@ static const struct JoinNode : public cmGeneratorExpressionNode const GeneratorExpressionContent* /*content*/, cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override { - std::vector<std::string> list = cmExpandedList(parameters.front()); - return cmJoin(list, parameters[1]); + return cmList{ parameters.front() }.join(parameters[1]); } } joinNode; diff --git a/Source/cmList.cxx b/Source/cmList.cxx new file mode 100644 index 0000000..39d138a --- /dev/null +++ b/Source/cmList.cxx @@ -0,0 +1,996 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmConfigure.h" // IWYU pragma: keep + +#include "cmList.h" + +#include <algorithm> +#include <cstddef> +#include <functional> +#include <iterator> +#include <set> +#include <stdexcept> +#include <utility> + +#include <cm/memory> + +#include "cmsys/RegularExpression.hxx" + +#include "cmAlgorithms.h" +#include "cmGeneratorExpression.h" +#include "cmRange.h" +#include "cmStringAlgorithms.h" +#include "cmStringReplaceHelper.h" +#include "cmSystemTools.h" + +cm::string_view cmList::element_separator{ ";" }; + +cmList cmList::sublist(size_type pos, size_type length) const +{ + if (pos >= this->Values.size()) { + throw std::out_of_range(cmStrCat( + "begin index: ", pos, " is out of range 0 - ", this->Values.size() - 1)); + } + + size_type count = (length == npos || pos + length > this->size()) + ? this->size() + : pos + length; + return this->sublist(this->begin() + pos, this->begin() + count); +} + +cmList::size_type cmList::find(cm::string_view value) const +{ + auto res = std::find(this->Values.begin(), this->Values.end(), value); + if (res == this->Values.end()) { + return npos; + } + + return std::distance(this->Values.begin(), res); +} + +cmList& cmList::remove_duplicates() +{ + auto newEnd = cmRemoveDuplicates(this->Values); + this->Values.erase(newEnd, this->Values.end()); + + return *this; +} + +namespace { +class MatchesRegex +{ +public: + MatchesRegex(cmsys::RegularExpression& regex, cmList::FilterMode mode) + : Regex(regex) + , IncludeMatches(mode == cmList::FilterMode::INCLUDE) + { + } + + bool operator()(const std::string& target) + { + return this->Regex.find(target) ^ this->IncludeMatches; + } + +private: + cmsys::RegularExpression& Regex; + const bool IncludeMatches; +}; +} + +cmList& cmList::filter(cm::string_view pattern, FilterMode mode) +{ + cmsys::RegularExpression regex(std::string{ pattern }); + if (!regex.is_valid()) { + throw std::invalid_argument(cmStrCat("invalid regex: ", pattern)); + } + + auto it = std::remove_if(this->Values.begin(), this->Values.end(), + MatchesRegex{ regex, mode }); + this->Values.erase(it, this->Values.end()); + + return *this; +} + +namespace { +class StringSorter +{ +protected: + using StringFilter = std::function<std::string(const std::string&)>; + + using OrderMode = cmList::SortConfiguration::OrderMode; + using CompareMethod = cmList::SortConfiguration::CompareMethod; + using CaseSensitivity = cmList::SortConfiguration::CaseSensitivity; + + StringFilter GetCompareFilter(CompareMethod compare) + { + return (compare == CompareMethod::FILE_BASENAME) + ? cmSystemTools::GetFilenameName + : nullptr; + } + + StringFilter GetCaseFilter(CaseSensitivity sensitivity) + { + return (sensitivity == CaseSensitivity::INSENSITIVE) + ? cmSystemTools::LowerCase + : nullptr; + } + + using ComparisonFunction = + std::function<bool(const std::string&, const std::string&)>; + ComparisonFunction GetComparisonFunction(CompareMethod compare) + { + if (compare == CompareMethod::NATURAL) { + return std::function<bool(const std::string&, const std::string&)>( + [](const std::string& x, const std::string& y) { + return cmSystemTools::strverscmp(x, y) < 0; + }); + } + return std::function<bool(const std::string&, const std::string&)>( + [](const std::string& x, const std::string& y) { return x < y; }); + } + +public: + StringSorter(cmList::SortConfiguration const& config) + : Filters{ this->GetCompareFilter(config.Compare), + this->GetCaseFilter(config.Case) } + , SortMethod(this->GetComparisonFunction(config.Compare)) + , Descending(config.Order == OrderMode::DESCENDING) + { + } + + std::string ApplyFilter(const std::string& argument) + { + std::string result = argument; + for (auto const& filter : this->Filters) { + if (filter != nullptr) { + result = filter(result); + } + } + return result; + } + + bool operator()(const std::string& a, const std::string& b) + { + std::string af = this->ApplyFilter(a); + std::string bf = this->ApplyFilter(b); + bool result; + if (this->Descending) { + result = this->SortMethod(bf, af); + } else { + result = this->SortMethod(af, bf); + } + return result; + } + +private: + StringFilter Filters[2] = { nullptr, nullptr }; + ComparisonFunction SortMethod; + bool Descending; +}; +} + +cmList::SortConfiguration::SortConfiguration() = default; + +cmList& cmList::sort(const SortConfiguration& cfg) +{ + SortConfiguration config{ cfg }; + + if (config.Order == SortConfiguration::OrderMode::DEFAULT) { + config.Order = SortConfiguration::OrderMode::ASCENDING; + } + if (config.Compare == SortConfiguration::CompareMethod::DEFAULT) { + config.Compare = SortConfiguration::CompareMethod::STRING; + } + if (config.Case == SortConfiguration::CaseSensitivity::DEFAULT) { + config.Case = SortConfiguration::CaseSensitivity::SENSITIVE; + } + + if ((config.Compare == SortConfiguration::CompareMethod::STRING) && + (config.Case == SortConfiguration::CaseSensitivity::SENSITIVE) && + (config.Order == SortConfiguration::OrderMode::ASCENDING)) { + std::sort(this->Values.begin(), this->Values.end()); + } else { + StringSorter sorter(config); + std::sort(this->Values.begin(), this->Values.end(), sorter); + } + + return *this; +} + +namespace { +using transform_type = std::function<std::string(const std::string&)>; +using transform_error = cmList::transform_error; + +class TransformSelector : public cmList::TransformSelector +{ +public: + ~TransformSelector() override = default; + + std::string Tag; + + const std::string& GetTag() override { return this->Tag; } + + virtual bool Validate(std::size_t count = 0) = 0; + + virtual bool InSelection(const std::string&) = 0; + + virtual void Transform(cmList::container_type& list, + const transform_type& transform) + { + std::transform(list.begin(), list.end(), list.begin(), transform); + } + +protected: + TransformSelector(std::string&& tag) + : Tag(std::move(tag)) + { + } +}; + +class TransformNoSelector : public TransformSelector +{ +public: + TransformNoSelector() + : TransformSelector("NO SELECTOR") + { + } + + bool Validate(std::size_t) override { return true; } + + bool InSelection(const std::string&) override { return true; } +}; +class TransformSelectorRegex : public TransformSelector +{ +public: + TransformSelectorRegex(const std::string& regex) + : TransformSelector("REGEX") + , Regex(regex) + { + } + TransformSelectorRegex(std::string&& regex) + : TransformSelector("REGEX") + , Regex(regex) + { + } + + bool Validate(std::size_t) override { return this->Regex.is_valid(); } + + bool InSelection(const std::string& value) override + { + return this->Regex.find(value); + } + + cmsys::RegularExpression Regex; +}; +class TransformSelectorIndexes : public TransformSelector +{ +public: + std::vector<index_type> Indexes; + + bool InSelection(const std::string&) override { return true; } + + void Transform(std::vector<std::string>& list, + const transform_type& transform) override + { + this->Validate(list.size()); + + for (auto index : this->Indexes) { + list[index] = transform(list[index]); + } + } + +protected: + TransformSelectorIndexes(std::string&& tag) + : TransformSelector(std::move(tag)) + { + } + TransformSelectorIndexes(std::string&& tag, std::vector<int> const& indexes) + : TransformSelector(std::move(tag)) + , Indexes(indexes) + { + } + TransformSelectorIndexes(std::string&& tag, std::vector<int>&& indexes) + : TransformSelector(std::move(tag)) + , Indexes(indexes) + { + } + + int NormalizeIndex(index_type index, std::size_t count) + { + if (index < 0) { + index = static_cast<index_type>(count) + index; + } + if (index < 0 || count <= static_cast<std::size_t>(index)) { + throw transform_error(cmStrCat( + "sub-command TRANSFORM, selector ", this->Tag, ", index: ", index, + " out of range (-", count, ", ", count - 1, ").")); + } + return index; + } +}; +class TransformSelectorAt : public TransformSelectorIndexes +{ +public: + TransformSelectorAt(std::vector<index_type> const& indexes) + : TransformSelectorIndexes("AT", indexes) + { + } + TransformSelectorAt(std::vector<index_type>&& indexes) + : TransformSelectorIndexes("AT", std::move(indexes)) + { + } + + bool Validate(std::size_t count) override + { + decltype(this->Indexes) indexes; + + for (auto index : this->Indexes) { + indexes.push_back(this->NormalizeIndex(index, count)); + } + this->Indexes = std::move(indexes); + + return true; + } +}; +class TransformSelectorFor : public TransformSelectorIndexes +{ +public: + TransformSelectorFor(int start, int stop, int step) + : TransformSelectorIndexes("FOR") + , Start(start) + , Stop(stop) + , Step(step) + { + } + + bool Validate(std::size_t count) override + { + this->Start = this->NormalizeIndex(this->Start, count); + this->Stop = this->NormalizeIndex(this->Stop, count); + + // Does stepping move us further from the end? + if (this->Start > this->Stop) { + throw transform_error( + cmStrCat("sub-command TRANSFORM, selector FOR " + "expects <start> to be no greater than <stop> (", + this->Start, " > ", this->Stop, ")")); + } + + // compute indexes + auto size = (this->Stop - this->Start + 1) / this->Step; + if ((this->Stop - this->Start + 1) % this->Step != 0) { + size += 1; + } + + this->Indexes.resize(size); + auto start = this->Start; + auto step = this->Step; + std::generate(this->Indexes.begin(), this->Indexes.end(), + [&start, step]() -> int { + auto r = start; + start += step; + return r; + }); + + return true; + } + +private: + index_type Start, Stop, Step; +}; + +class TransformAction +{ +public: + virtual ~TransformAction() = default; + + void Initialize(TransformSelector* selector) { this->Selector = selector; } + virtual void Initialize(TransformSelector*, const std::string&) {} + virtual void Initialize(TransformSelector*, const std::string&, + const std::string&) + { + } + virtual void Initialize(TransformSelector* selector, + const std::vector<std::string>&) + { + this->Initialize(selector); + } + + virtual std::string operator()(const std::string& s) = 0; + +protected: + TransformSelector* Selector; +}; +class TransformActionAppend : public TransformAction +{ +public: + using TransformAction::Initialize; + + void Initialize(TransformSelector* selector, + const std::string& append) override + { + TransformAction::Initialize(selector); + this->Append = append; + } + void Initialize(TransformSelector* selector, + const std::vector<std::string>& append) override + { + this->Initialize(selector, append.front()); + } + + std::string operator()(const std::string& s) override + { + if (this->Selector->InSelection(s)) { + return cmStrCat(s, this->Append); + } + + return s; + } + +private: + std::string Append; +}; +class TransformActionPrepend : public TransformAction +{ +public: + using TransformAction::Initialize; + + void Initialize(TransformSelector* selector, + const std::string& prepend) override + { + TransformAction::Initialize(selector); + this->Prepend = prepend; + } + void Initialize(TransformSelector* selector, + const std::vector<std::string>& prepend) override + { + this->Initialize(selector, prepend.front()); + } + + std::string operator()(const std::string& s) override + { + if (this->Selector->InSelection(s)) { + return cmStrCat(this->Prepend, s); + } + + return s; + } + +private: + std::string Prepend; +}; +class TransformActionToUpper : public TransformAction +{ +public: + std::string operator()(const std::string& s) override + { + if (this->Selector->InSelection(s)) { + return cmSystemTools::UpperCase(s); + } + + return s; + } +}; +class TransformActionToLower : public TransformAction +{ +public: + std::string operator()(const std::string& s) override + { + if (this->Selector->InSelection(s)) { + return cmSystemTools::LowerCase(s); + } + + return s; + } +}; +class TransformActionStrip : public TransformAction +{ +public: + std::string operator()(const std::string& s) override + { + if (this->Selector->InSelection(s)) { + return cmTrimWhitespace(s); + } + + return s; + } +}; +class TransformActionGenexStrip : public TransformAction +{ +public: + std::string operator()(const std::string& s) override + { + if (this->Selector->InSelection(s)) { + return cmGeneratorExpression::Preprocess( + s, cmGeneratorExpression::StripAllGeneratorExpressions); + } + + return s; + } +}; +class TransformActionReplace : public TransformAction +{ +public: + using TransformAction::Initialize; + + void Initialize(TransformSelector* selector, const std::string& regex, + const std::string& replace) override + { + TransformAction::Initialize(selector); + this->ReplaceHelper = + cm::make_unique<cmStringReplaceHelper>(regex, replace); + + if (!this->ReplaceHelper->IsRegularExpressionValid()) { + throw transform_error( + cmStrCat("sub-command TRANSFORM, action REPLACE: Failed to compile " + "regex \"", + regex, "\".")); + } + if (!this->ReplaceHelper->IsReplaceExpressionValid()) { + throw transform_error(cmStrCat("sub-command TRANSFORM, action REPLACE: ", + this->ReplaceHelper->GetError(), ".")); + } + } + void Initialize(TransformSelector* selector, + const std::vector<std::string>& args) override + { + this->Initialize(selector, args[0], args[1]); + } + + std::string operator()(const std::string& s) override + { + if (this->Selector->InSelection(s)) { + // Scan through the input for all matches. + std::string output; + + if (!this->ReplaceHelper->Replace(s, output)) { + throw transform_error( + cmStrCat("sub-command TRANSFORM, action REPLACE: ", + this->ReplaceHelper->GetError(), ".")); + } + + return output; + } + + return s; + } + +private: + std::unique_ptr<cmStringReplaceHelper> ReplaceHelper; +}; + +// Descriptor of action +// Arity: number of arguments required for the action +// Transform: Object implementing the action +struct ActionDescriptor +{ + ActionDescriptor(cmList::TransformAction action) + : Action(action) + { + } + ActionDescriptor(cmList::TransformAction action, std::string name, + std::size_t arity, + std::unique_ptr<TransformAction> transform) + : Action(action) + , Name(std::move(name)) + , Arity(arity) + , Transform(std::move(transform)) + { + } + + operator cmList::TransformAction() const { return this->Action; } + + cmList::TransformAction Action; + std::string Name; + std::size_t Arity = 0; + std::unique_ptr<TransformAction> Transform; +}; + +// Build a set of supported actions. +using ActionDescriptorSet = std::set< + ActionDescriptor, + std::function<bool(cmList::TransformAction, cmList::TransformAction)>>; + +ActionDescriptorSet Descriptors([](cmList::TransformAction x, + cmList::TransformAction y) { + return x < y; +}); + +ActionDescriptorSet::iterator TransformConfigure( + cmList::TransformAction action, + std::unique_ptr<cmList::TransformSelector>& selector, std::size_t arity) +{ + if (Descriptors.empty()) { + Descriptors.emplace(cmList::TransformAction::APPEND, "APPEND", 1, + cm::make_unique<TransformActionAppend>()); + Descriptors.emplace(cmList::TransformAction::PREPEND, "PREPEND", 1, + cm::make_unique<TransformActionPrepend>()); + Descriptors.emplace(cmList::TransformAction::TOUPPER, "TOUPPER", 0, + cm::make_unique<TransformActionToUpper>()); + Descriptors.emplace(cmList::TransformAction::TOLOWER, "TOLOWER", 0, + cm::make_unique<TransformActionToLower>()); + Descriptors.emplace(cmList::TransformAction::STRIP, "STRIP", 0, + cm::make_unique<TransformActionStrip>()); + Descriptors.emplace(cmList::TransformAction::GENEX_STRIP, "GENEX_STRIP", 0, + cm::make_unique<TransformActionGenexStrip>()); + Descriptors.emplace(cmList::TransformAction::REPLACE, "REPLACE", 2, + cm::make_unique<TransformActionReplace>()); + } + + auto descriptor = Descriptors.find(action); + if (descriptor == Descriptors.end()) { + throw transform_error(cmStrCat(" sub-command TRANSFORM, ", + std::to_string(static_cast<int>(action)), + " invalid action.")); + } + + if (descriptor->Arity != arity) { + throw transform_error(cmStrCat("sub-command TRANSFORM, action ", + descriptor->Name, " expects ", + descriptor->Arity, " argument(s).")); + } + if (!selector) { + selector = cm::make_unique<TransformNoSelector>(); + } + + return descriptor; +} +} + +std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewAT( + std::initializer_list<index_type> indexes) +{ + return cm::make_unique<TransformSelectorAt>( + std::vector<index_type>{ indexes.begin(), indexes.end() }); + ; +} +std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewAT( + std::vector<index_type> const& indexes) +{ + return cm::make_unique<TransformSelectorAt>(indexes); +} +std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewAT( + std::vector<index_type>&& indexes) +{ + return cm::make_unique<TransformSelectorAt>(std::move(indexes)); +} + +std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewFOR( + std::initializer_list<index_type> indexes) +{ + if (indexes.size() < 2 || indexes.size() > 3) { + throw transform_error("sub-command TRANSFORM, selector FOR " + "expects 2 or 3 arguments"); + } + if (indexes.size() == 3 && *(indexes.begin() + 2) < 0) { + throw transform_error("sub-command TRANSFORM, selector FOR expects " + "positive numeric value for <step>."); + } + + return cm::make_unique<TransformSelectorFor>( + *indexes.begin(), *(indexes.begin() + 1), + indexes.size() == 3 ? *(indexes.begin() + 2) : 1); +} +std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewFOR( + std::vector<index_type> const& indexes) +{ + if (indexes.size() < 2 || indexes.size() > 3) { + throw transform_error("sub-command TRANSFORM, selector FOR " + "expects 2 or 3 arguments"); + } + if (indexes.size() == 3 && indexes[2] < 0) { + throw transform_error("sub-command TRANSFORM, selector FOR expects " + "positive numeric value for <step>."); + } + + return cm::make_unique<TransformSelectorFor>( + indexes[0], indexes[1], indexes.size() == 3 ? indexes[2] : 1); +} +std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewFOR( + std::vector<index_type>&& indexes) +{ + if (indexes.size() < 2 || indexes.size() > 3) { + throw transform_error("sub-command TRANSFORM, selector FOR " + "expects 2 or 3 arguments"); + } + if (indexes.size() == 3 && indexes[2] < 0) { + throw transform_error("sub-command TRANSFORM, selector FOR expects " + "positive numeric value for <step>."); + } + + return cm::make_unique<TransformSelectorFor>( + indexes[0], indexes[1], indexes.size() == 3 ? indexes[2] : 1); +} + +std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewREGEX( + std::string const& regex) +{ + std::unique_ptr<::TransformSelector> selector = + cm::make_unique<TransformSelectorRegex>(regex); + if (!selector->Validate()) { + throw transform_error( + cmStrCat("sub-command TRANSFORM, selector REGEX failed to compile " + "regex \"", + regex, "\".")); + } + // weird construct to please all compilers + return std::unique_ptr<cmList::TransformSelector>(selector.release()); +} +std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewREGEX( + std::string&& regex) +{ + std::unique_ptr<::TransformSelector> selector = + cm::make_unique<TransformSelectorRegex>(std::move(regex)); + if (!selector->Validate()) { + throw transform_error( + cmStrCat("sub-command TRANSFORM, selector REGEX failed to compile " + "regex \"", + regex, "\".")); + } + // weird construct to please all compilers + return std::unique_ptr<cmList::TransformSelector>(selector.release()); +} + +cmList& cmList::transform(TransformAction action, + std::unique_ptr<TransformSelector> selector) +{ + auto descriptor = TransformConfigure(action, selector, 0); + + descriptor->Transform->Initialize( + static_cast<::TransformSelector*>(selector.get())); + + static_cast<::TransformSelector&>(*selector).Transform( + this->Values, [&descriptor](const std::string& s) -> std::string { + return (*descriptor->Transform)(s); + }); + + return *this; +} + +cmList& cmList::transform(TransformAction action, std::string const& arg, + std::unique_ptr<TransformSelector> selector) +{ + auto descriptor = TransformConfigure(action, selector, 1); + + descriptor->Transform->Initialize( + static_cast<::TransformSelector*>(selector.get()), arg); + + static_cast<::TransformSelector&>(*selector).Transform( + this->Values, [&descriptor](const std::string& s) -> std::string { + return (*descriptor->Transform)(s); + }); + + return *this; +} + +cmList& cmList::transform(TransformAction action, std::string const& arg1, + std::string const& arg2, + std::unique_ptr<TransformSelector> selector) +{ + auto descriptor = TransformConfigure(action, selector, 2); + + descriptor->Transform->Initialize( + static_cast<::TransformSelector*>(selector.get()), arg1, arg2); + + static_cast<::TransformSelector&>(*selector).Transform( + this->Values, [&descriptor](const std::string& s) -> std::string { + return (*descriptor->Transform)(s); + }); + + return *this; +} + +cmList& cmList::transform(TransformAction action, + std::vector<std::string> const& args, + std::unique_ptr<TransformSelector> selector) +{ + auto descriptor = TransformConfigure(action, selector, args.size()); + + descriptor->Transform->Initialize( + static_cast<::TransformSelector*>(selector.get()), args); + + static_cast<::TransformSelector&>(*selector).Transform( + this->Values, [&descriptor](const std::string& s) -> std::string { + return (*descriptor->Transform)(s); + }); + + return *this; +} + +std::string cmList::join(cm::string_view glue) const +{ + return cmJoin(this->Values, glue); +} + +std::string& cmList::append(cm::string_view value, std::string& list) +{ + if (list.empty()) { + list = std::string(value); + } else { + list += cmStrCat(cmList::element_separator, value); + } + + return list; +} + +std::string& cmList::prepend(cm::string_view value, std::string& list) +{ + if (list.empty()) { + list = std::string(value); + } else { + list.insert(0, cmStrCat(value, cmList::element_separator)); + } + + return list; +} + +cmList::size_type cmList::ComputeIndex(index_type pos, bool boundCheck) const +{ + if (boundCheck) { + if (this->Values.empty()) { + throw std::out_of_range( + cmStrCat("index: ", pos, " out of range (0, 0)")); + } + + if (!this->Values.empty()) { + auto length = this->Values.size(); + if (pos < 0) { + pos = static_cast<index_type>(length) + pos; + } + if (pos < 0 || length <= static_cast<size_type>(pos)) { + throw std::out_of_range(cmStrCat("index: ", pos, " out of range (-", + this->Values.size(), ", ", + this->Values.size() - 1, ")")); + } + } + return pos; + } + + return pos < 0 ? this->Values.size() + pos : pos; +} +cmList::size_type cmList::ComputeInsertIndex(index_type pos, + bool boundCheck) const +{ + if (boundCheck) { + if (this->Values.empty() && pos != 0) { + throw std::out_of_range( + cmStrCat("index: ", pos, " out of range (0, 0)")); + } + + if (!this->Values.empty()) { + auto length = this->Values.size(); + if (pos < 0) { + pos = static_cast<index_type>(length) + pos; + } + if (pos < 0 || length < static_cast<size_type>(pos)) { + throw std::out_of_range(cmStrCat("index: ", pos, " out of range (-", + this->Values.size(), ", ", + this->Values.size(), ")")); + } + } + return pos; + } + + return pos < 0 ? this->Values.size() + pos : pos; +} + +cmList cmList::GetItems(std::vector<index_type>&& indexes) const +{ + cmList listItems; + + for (auto index : indexes) { + listItems.emplace_back(this->at(index)); + } + + return listItems; +} + +cmList& cmList::RemoveItems(std::vector<index_type>&& indexes) +{ + if (indexes.empty()) { + return *this; + } + + // compute all indexes + std::vector<size_type> idx(indexes.size()); + std::transform( + indexes.cbegin(), indexes.cend(), idx.begin(), + [this](const index_type& index) { return this->ComputeIndex(index); }); + + std::sort(idx.begin(), idx.end(), + [](size_type l, size_type r) { return l > r; }); + auto newEnd = std::unique(idx.begin(), idx.end()); + idx.erase(newEnd, idx.end()); + + for (auto index : idx) { + this->erase(this->begin() + index); + } + + return *this; +} + +cmList& cmList::RemoveItems(std::vector<std::string>&& items) +{ + std::sort(items.begin(), items.end()); + auto last = std::unique(items.begin(), items.end()); + auto first = items.begin(); + + auto newEnd = cmRemoveMatching(this->Values, cmMakeRange(first, last)); + this->Values.erase(newEnd, this->Values.end()); + + return *this; +} + +cmList::container_type::iterator cmList::Insert( + container_type::const_iterator pos, std::string&& value, + container_type& container, ExpandElements expandElements, + EmptyElements emptyElements) +{ + auto delta = std::distance(container.cbegin(), pos); + auto insertPos = container.begin() + delta; + + if (expandElements == ExpandElements::Yes) { + // If argument is empty, it is an empty list. + if (emptyElements == EmptyElements::No && value.empty()) { + return insertPos; + } + + // if there are no ; in the name then just copy the current string + if (value.find(';') == std::string::npos) { + return container.insert(insertPos, std::move(value)); + } + + std::string newValue; + // Break the string at non-escaped semicolons not nested in []. + int squareNesting = 0; + auto last = value.begin(); + auto const cend = value.end(); + for (auto c = last; c != cend; ++c) { + switch (*c) { + case '\\': { + // We only want to allow escaping of semicolons. Other + // escapes should not be processed here. + auto cnext = c + 1; + if ((cnext != cend) && *cnext == ';') { + newValue.append(last, c); + // Skip over the escape character + last = cnext; + c = cnext; + } + } break; + case '[': { + ++squareNesting; + } break; + case ']': { + --squareNesting; + } break; + case ';': { + // brackets. + if (squareNesting == 0) { + newValue.append(last, c); + // Skip over the semicolon + last = c + 1; + if (!newValue.empty() || emptyElements == EmptyElements::Yes) { + // Add the last argument. + insertPos = container.insert(insertPos, newValue); + insertPos++; + newValue.clear(); + } + } + } break; + default: { + // Just append this character. + } break; + } + } + newValue.append(last, cend); + if (!newValue.empty() || emptyElements == EmptyElements::Yes) { + // Add the last argument. + container.insert(insertPos, std::move(newValue)); + } + } else if (!value.empty() || emptyElements == EmptyElements::Yes) { + return container.insert(insertPos, std::move(value)); + } + return container.begin() + delta; +} diff --git a/Source/cmList.h b/Source/cmList.h new file mode 100644 index 0000000..d994ad3 --- /dev/null +++ b/Source/cmList.h @@ -0,0 +1,1198 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <algorithm> +#include <initializer_list> +#include <iterator> +#include <memory> +#include <numeric> +#include <stdexcept> +#include <string> +#include <utility> +#include <vector> + +#include <cm/string_view> + +#include "cmValue.h" + +/** + * CMake lists management + * + * For all operations, input arguments (single value like cm::string_view or + * multiple values specified through pair of iterators) are, by default, + * expanded. The expansion can be controlled by the cmList::ExpandElements + * option. + * + * There is an exception to this rule. The following methods do not expand + * their argument: cmList::push_back, cmList::emplace and cmList::emplace_back. + */ + +class cmList +{ +public: + using container_type = std::vector<std::string>; + + using value_type = container_type::value_type; + using allocator_type = container_type::allocator_type; + using index_type = int; + using size_type = container_type::size_type; + using difference_type = container_type::difference_type; + using reference = container_type::reference; + using const_reference = container_type::const_reference; + using iterator = container_type::iterator; + using const_iterator = container_type::const_iterator; + using reverse_iterator = container_type::reverse_iterator; + using const_reverse_iterator = container_type::const_reverse_iterator; + + static const size_type npos = static_cast<size_type>(-1); + + static cm::string_view element_separator; + + enum class EmptyElements + { + No, + Yes, + }; + enum class ExpandElements + { + No, + Yes, + }; + + cmList() = default; + cmList(const cmList&) = default; + cmList(cmList&&) = default; + + cmList(cm::string_view value, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + this->assign(value, expandElements, emptyElements); + } + cmList(cm::string_view value, EmptyElements emptyElements) + { + this->assign(value, ExpandElements::Yes, emptyElements); + } + cmList(cmValue list, ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + if (list) { + this->assign(*list, expandElements, emptyElements); + } + } + cmList(cmValue list, EmptyElements emptyElements) + : cmList(list, ExpandElements::Yes, emptyElements) + { + } + template <typename InputIterator> + cmList(InputIterator first, InputIterator last, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + this->assign(first, last, expandElements, emptyElements); + } + template <typename InputIterator> + cmList(InputIterator first, InputIterator last, EmptyElements emptyElements) + : cmList(first, last, ExpandElements::Yes, emptyElements) + { + } + cmList(const container_type& init, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + : cmList(init.begin(), init.end(), expandElements, emptyElements) + { + } + cmList(const container_type& init, EmptyElements emptyElements) + : cmList(init, ExpandElements::Yes, emptyElements) + { + } + cmList(container_type&& init, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + : cmList(std::make_move_iterator(init.begin()), + std::make_move_iterator(init.end()), expandElements, + emptyElements) + { + init.clear(); + } + cmList(container_type&& init, EmptyElements emptyElements) + : cmList(std::move(init), ExpandElements::Yes, emptyElements) + { + } + cmList(std::initializer_list<std::string> init) { this->assign(init); } + + ~cmList() = default; + + cmList& operator=(const cmList&) = default; + cmList& operator=(cmList&&) = default; + + cmList& operator=(cm::string_view value) + { + this->assign(value); + return *this; + } + cmList& operator=(cmValue value) + { + if (value) { + this->operator=(*value); + } else { + this->clear(); + } + + return *this; + } + + cmList& operator=(const container_type& init) + { + this->assign(init); + return *this; + } + cmList& operator=(container_type&& init) + { + this->assign(std::move(init)); + + return *this; + } + + cmList& operator=(std::initializer_list<std::string> init) + { + this->assign(init); + return *this; + } + + void assign(cm::string_view value, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + this->clear(); + this->append(value, expandElements, emptyElements); + } + void assign(cm::string_view value, EmptyElements emptyElements) + { + this->assign(value, ExpandElements::Yes, emptyElements); + } + void assign(cmValue value, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + if (value) { + this->assign(*value, expandElements, emptyElements); + } else { + this->clear(); + } + } + void assign(cmValue value, EmptyElements emptyElements) + { + this->assign(value, ExpandElements::Yes, emptyElements); + } + template <typename InputIterator> + void assign(InputIterator first, InputIterator last, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + this->clear(); + this->append(first, last, expandElements, emptyElements); + } + template <typename InputIterator> + void assign(InputIterator first, InputIterator last, + EmptyElements emptyElements) + { + this->assign(first, last, ExpandElements::Yes, emptyElements); + } + void assign(const cmList& init, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + this->assign(init.begin(), init.end(), expandElements, emptyElements); + } + void assign(const cmList& init, EmptyElements emptyElements) + { + this->assign(init, ExpandElements::Yes, emptyElements); + } + void assign(cmList&& init, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + this->assign(std::make_move_iterator(init.begin()), + std::make_move_iterator(init.end()), expandElements, + emptyElements); + init.clear(); + } + void assign(cmList&& init, EmptyElements emptyElements) + { + this->assign(std::move(init), ExpandElements::Yes, emptyElements); + } + void assign(const container_type& init, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + this->assign(init.begin(), init.end(), expandElements, emptyElements); + } + void assign(const container_type& init, EmptyElements emptyElements) + { + this->assign(init, ExpandElements::Yes, emptyElements); + } + void assign(container_type&& init, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + this->assign(std::make_move_iterator(init.begin()), + std::make_move_iterator(init.end()), expandElements, + emptyElements); + init.clear(); + } + void assign(container_type&& init, EmptyElements emptyElements) + { + this->assign(std::move(init), ExpandElements::Yes, emptyElements); + } + void assign(std::initializer_list<std::string> init) + { + this->assign(init.begin(), init.end()); + } + + // Conversions + std::string to_string() const + { + return this->join(cmList::element_separator); + } + + operator container_type&() & noexcept { return this->Values; } + operator const container_type&() const& noexcept { return this->Values; } + operator container_type&&() && noexcept { return std::move(this->Values); } + + // Element access + reference at(index_type pos) + { + return this->Values.at(this->ComputeIndex(pos)); + } + const_reference at(index_type pos) const + { + return this->Values.at(this->ComputeIndex(pos)); + } + + reference operator[](index_type pos) + { + return this->Values[this->ComputeIndex(pos, false)]; + } + const_reference operator[](index_type pos) const + { + return this->Values[this->ComputeIndex(pos, false)]; + } + + reference front() { return this->Values.front(); } + const_reference front() const { return this->Values.front(); } + + reference back() { return this->Values.back(); } + const_reference back() const { return this->Values.back(); } + + // extract sublist in range [first, last) + cmList sublist(const_iterator first, const_iterator last) const + { + return cmList{ first, last }; + } + // Extract sublist in range [first, last) + // Throw std::out_of_range if pos is invalid + cmList sublist(size_type pos = 0, size_type length = npos) const; + + // Returns the list of elements + // Throw std::out_of_range if any index is invalid + cmList get_items(std::initializer_list<index_type> indexes) const + { + return this->GetItems( + std::vector<index_type>{ indexes.begin(), indexes.end() }); + } + template <typename InputIterator> + cmList get_items(InputIterator first, InputIterator last) const + { + return this->GetItems(std::vector<index_type>{ first, last }); + } + + size_type find(cm::string_view value) const; + size_type find(cmValue value) const + { + if (value) { + return this->find(*value); + } + + return npos; + } + + container_type& data() noexcept { return this->Values; } + const container_type& data() const noexcept { return this->Values; } + + // Iterators + iterator begin() noexcept { return this->Values.begin(); } + const_iterator begin() const noexcept { return this->Values.begin(); } + const_iterator cbegin() const noexcept { return this->Values.cbegin(); } + + iterator end() noexcept { return this->Values.end(); } + const_iterator end() const noexcept { return this->Values.end(); } + const_iterator cend() const noexcept { return this->Values.cend(); } + + reverse_iterator rbegin() noexcept { return this->Values.rbegin(); } + const_reverse_iterator rbegin() const noexcept + { + return this->Values.rbegin(); + } + const_reverse_iterator crbegin() const noexcept + { + return this->Values.crbegin(); + } + + reverse_iterator rend() noexcept { return this->Values.rend(); } + const_reverse_iterator rend() const noexcept { return this->Values.rend(); } + const_reverse_iterator crend() const noexcept + { + return this->Values.crend(); + } + + // Capacity + bool empty() const noexcept { return this->Values.empty(); } + size_type size() const noexcept { return this->Values.size(); } + + // Modifiers + void clear() noexcept { this->Values.clear(); } + + iterator insert(const_iterator pos, cm::string_view value, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + return cmList::Insert(pos, std::string(value), this->Values, + expandElements, emptyElements); + } + iterator insert(const_iterator pos, cm::string_view value, + EmptyElements emptyElements) + { + return this->insert(pos, value, ExpandElements::Yes, emptyElements); + } + iterator insert(const_iterator pos, cmValue value, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + if (value) { + return this->insert(pos, *value, expandElements, emptyElements); + } + + auto delta = std::distance(this->cbegin(), pos); + return this->begin() + delta; + } + iterator insert(const_iterator pos, cmValue value, + EmptyElements emptyElements) + { + return this->insert(pos, value, ExpandElements::Yes, emptyElements); + } + template <typename InputIterator> + iterator insert(const_iterator pos, InputIterator first, InputIterator last, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + return cmList::Insert(pos, first, last, this->Values, expandElements, + emptyElements); + } + template <typename InputIterator> + iterator insert(const_iterator pos, InputIterator first, InputIterator last, + EmptyElements emptyElements) + { + return this->insert(pos, first, last, ExpandElements::Yes, emptyElements); + } + iterator insert(const_iterator pos, const cmList& values, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + return this->insert(pos, values.begin(), values.end(), expandElements, + emptyElements); + } + iterator insert(const_iterator pos, const cmList& values, + EmptyElements emptyElements) + { + return this->insert(pos, values, ExpandElements::Yes, emptyElements); + } + iterator insert(const_iterator pos, cmList&& values, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + auto result = this->insert(pos, std::make_move_iterator(values.begin()), + std::make_move_iterator(values.end()), + expandElements, emptyElements); + values.clear(); + + return result; + } + iterator insert(const_iterator pos, cmList&& values, + EmptyElements emptyElements) + { + return this->insert(pos, std::move(values), ExpandElements::Yes, + emptyElements); + } + iterator insert(const_iterator pos, const container_type& values, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + return this->insert(pos, values.begin(), values.end(), expandElements, + emptyElements); + } + iterator insert(const_iterator pos, const container_type& values, + EmptyElements emptyElements) + { + return this->insert(pos, values, ExpandElements::Yes, emptyElements); + } + iterator insert(const_iterator pos, container_type&& values, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + auto result = this->insert(pos, std::make_move_iterator(values.begin()), + std::make_move_iterator(values.end()), + expandElements, emptyElements); + values.clear(); + + return result; + } + iterator insert(const_iterator pos, container_type&& values, + EmptyElements emptyElements) + { + return this->insert(pos, std::move(values), ExpandElements::Yes, + emptyElements); + } + iterator insert(const_iterator pos, std::initializer_list<std::string> ilist) + { + return this->insert(pos, ilist.begin(), ilist.end()); + } + + iterator append(cm::string_view value, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + return this->insert(this->cend(), value, expandElements, emptyElements); + } + iterator append(cm::string_view value, EmptyElements emptyElements) + { + return this->append(value, ExpandElements::Yes, emptyElements); + } + iterator append(cmValue value, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + if (value) { + return this->append(*value, expandElements, emptyElements); + } + + return this->end(); + } + iterator append(cmValue value, EmptyElements emptyElements) + { + return this->append(value, ExpandElements::Yes, emptyElements); + } + template <typename InputIterator> + iterator append(InputIterator first, InputIterator last, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + return this->insert(this->cend(), first, last, expandElements, + emptyElements); + } + template <typename InputIterator> + iterator append(InputIterator first, InputIterator last, + EmptyElements emptyElements) + { + return this->append(first, last, ExpandElements::Yes, emptyElements); + } + iterator append(const cmList& values, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + return this->append(values.begin(), values.end(), expandElements, + emptyElements); + } + iterator append(const cmList& values, EmptyElements emptyElements) + { + return this->append(values, ExpandElements::Yes, emptyElements); + } + iterator append(cmList&& values, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + auto result = this->append(std::make_move_iterator(values.begin()), + std::make_move_iterator(values.end()), + expandElements, emptyElements); + values.clear(); + + return result; + } + iterator append(cmList&& values, EmptyElements emptyElements) + { + return this->append(std::move(values), ExpandElements::Yes, emptyElements); + } + iterator append(const container_type& values, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + return this->append(values.begin(), values.end(), expandElements, + emptyElements); + } + iterator append(const container_type& values, EmptyElements emptyElements) + { + return this->append(values, ExpandElements::Yes, emptyElements); + } + iterator append(container_type&& values, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + auto result = this->append(std::make_move_iterator(values.begin()), + std::make_move_iterator(values.end()), + expandElements, emptyElements); + values.clear(); + + return result; + } + iterator append(container_type&& values, EmptyElements emptyElements) + { + return this->append(std::move(values), ExpandElements::Yes, emptyElements); + } + iterator append(std::initializer_list<std::string> ilist) + { + return this->insert(this->cend(), ilist); + } + + iterator prepend(cm::string_view value, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + return this->insert(this->cbegin(), value, expandElements, emptyElements); + } + iterator prepend(cm::string_view value, EmptyElements emptyElements) + { + return this->prepend(value, ExpandElements::Yes, emptyElements); + } + iterator prepend(cmValue value, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + if (value) { + return this->prepend(*value, expandElements, emptyElements); + } + + return this->begin(); + } + iterator prepend(cmValue value, EmptyElements emptyElements) + { + return this->prepend(value, ExpandElements::Yes, emptyElements); + } + template <typename InputIterator> + iterator prepend(InputIterator first, InputIterator last, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + return this->insert(this->cbegin(), first, last, expandElements, + emptyElements); + } + template <typename InputIterator> + iterator prepend(InputIterator first, InputIterator last, + EmptyElements emptyElements) + { + return this->prepend(first, last, ExpandElements::Yes, emptyElements); + } + iterator prepend(const cmList& values, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + return this->prepend(values.begin(), values.end(), expandElements, + emptyElements); + } + iterator prepend(const cmList& values, EmptyElements emptyElements) + { + return this->prepend(values, ExpandElements::Yes, emptyElements); + } + iterator prepend(cmList&& values, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + auto result = this->prepend(std::make_move_iterator(values.begin()), + std::make_move_iterator(values.end()), + expandElements, emptyElements); + values.clear(); + + return result; + } + iterator prepend(cmList&& values, EmptyElements emptyElements) + { + return this->prepend(std::move(values), ExpandElements::Yes, + emptyElements); + } + iterator prepend(const container_type& values, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + return this->prepend(values.begin(), values.end(), expandElements, + emptyElements); + } + iterator prepend(const container_type& values, EmptyElements emptyElements) + { + return this->prepend(values, ExpandElements::Yes, emptyElements); + } + iterator prepend(container_type&& values, + ExpandElements expandElements = ExpandElements::Yes, + EmptyElements emptyElements = EmptyElements::No) + { + auto result = this->prepend(std::make_move_iterator(values.begin()), + std::make_move_iterator(values.end()), + expandElements, emptyElements); + values.clear(); + + return result; + } + iterator prepend(container_type&& values, EmptyElements emptyElements) + { + return this->prepend(std::move(values), ExpandElements::Yes, + emptyElements); + } + iterator prepend(std::initializer_list<std::string> ilist) + { + return this->insert(this->cbegin(), ilist); + } + + void push_back(cm::string_view value) + { + this->Values.push_back(std::string{ value }); + } + void push_back(std::string&& value) + { + this->Values.push_back(std::move(value)); + } + + template <typename... Args> + iterator emplace(const_iterator pos, Args&&... args) + { + return this->Values.emplace(pos, std::forward<Args>(args)...); + } + + template <typename... Args> + void emplace_back(Args&&... args) + { + this->Values.emplace_back(std::forward<Args>(args)...); + } + + // Inserts elements in the list + // Throw std::out_of_range if index is invalid + template <typename InputIterator> + cmList& insert_items(index_type index, InputIterator first, + InputIterator last) + { + this->insert(this->begin() + this->ComputeInsertIndex(index), first, last); + return *this; + } + cmList& insert_items(index_type index, + std::initializer_list<std::string> values) + { + return this->insert_items(index, values.begin(), values.end()); + } + + iterator erase(const_iterator pos) + { + // convert const_iterator in iterator to please non standard c++11 + // compilers (gcc 4.8 for example) + auto pos2 = + this->Values.begin() + std::distance(this->Values.cbegin(), pos); + return this->Values.erase(pos2); + } + iterator erase(const_iterator first, const_iterator last) + { + // convert const_iterator in iterator to please non standard c++11 + // compilers (gcc 4.8 for example) + auto first2 = + this->Values.begin() + std::distance(this->Values.cbegin(), first); + auto last2 = + this->Values.begin() + std::distance(this->Values.cbegin(), last); + return this->Values.erase(first2, last2); + } + + void pop_back() { this->Values.pop_back(); } + void pop_front() { this->Values.erase(this->begin()); } + + // Removes elements from the list + // Throw std::out_of_range if any index is invalid + cmList& remove_items(std::initializer_list<index_type> indexes) + { + return this->RemoveItems( + std::vector<index_type>{ indexes.begin(), indexes.end() }); + } + cmList& remove_items(std::initializer_list<std::string> values) + { + return this->RemoveItems( + std::vector<std::string>{ values.begin(), values.end() }); + } + template <typename InputIterator> + cmList& remove_items(InputIterator first, InputIterator last) + { + return this->RemoveItems( + std::vector<typename InputIterator::value_type>{ first, last }); + } + + cmList& remove_duplicates(); + + enum class FilterMode + { + INCLUDE, + EXCLUDE + }; + // Includes or removes items from the list + // Throw std::invalid_argument if regular expression is invalid + cmList& filter(cm::string_view regex, FilterMode mode); + + cmList& reverse() + { + std::reverse(this->Values.begin(), this->Values.end()); + return *this; + } + + struct SortConfiguration + { + enum class OrderMode + { + DEFAULT, + ASCENDING, + DESCENDING, + } Order = OrderMode::DEFAULT; + + enum class CompareMethod + { + DEFAULT, + STRING, + FILE_BASENAME, + NATURAL, + } Compare = CompareMethod::DEFAULT; + + enum class CaseSensitivity + { + DEFAULT, + SENSITIVE, + INSENSITIVE, + } Case = CaseSensitivity::DEFAULT; + + // declare the default constructor to work-around clang bug + SortConfiguration(); + + SortConfiguration(OrderMode order, CompareMethod compare, + CaseSensitivity caseSensitivity) + : Order(order) + , Compare(compare) + , Case(caseSensitivity) + { + } + }; + cmList& sort(const SortConfiguration& config = SortConfiguration{}); + + // exception raised on error during transform operations + class transform_error : public std::runtime_error + { + public: + transform_error(const std::string& error) + : std::runtime_error(error) + { + } + }; + + class TransformSelector + { + public: + using index_type = cmList::index_type; + + // define some structs used as template selector + struct AT; + struct FOR; + struct REGEX; + + virtual ~TransformSelector() = default; + + virtual const std::string& GetTag() = 0; + + // method NEW is used to allocate a selector of the needed type. + // For example: + // cmList::TransformSelector::New<AT>({1, 2, 5, 6}); + // or + // cmList::TransformSelector::New<REGEX>("^XX.*"); + template <typename Type> + static std::unique_ptr<TransformSelector> New( + std::initializer_list<index_type>); + template <typename Type> + static std::unique_ptr<TransformSelector> New( + std::vector<index_type> const&); + template <typename Type> + static std::unique_ptr<TransformSelector> New(std::vector<index_type>&&); + + template <typename Type> + static std::unique_ptr<TransformSelector> New(std::string const&); + template <typename Type> + static std::unique_ptr<TransformSelector> New(std::string&&); + + private: + static std::unique_ptr<TransformSelector> NewAT( + std::initializer_list<index_type> init); + static std::unique_ptr<TransformSelector> NewAT( + std::vector<index_type> const& init); + static std::unique_ptr<TransformSelector> NewAT( + std::vector<index_type>&& init); + + static std::unique_ptr<TransformSelector> NewFOR( + std::initializer_list<index_type> init); + static std::unique_ptr<TransformSelector> NewFOR( + std::vector<index_type> const& init); + static std::unique_ptr<TransformSelector> NewFOR( + std::vector<index_type>&& init); + + static std::unique_ptr<TransformSelector> NewREGEX( + std::string const& init); + static std::unique_ptr<TransformSelector> NewREGEX(std::string&& init); + }; + + enum class TransformAction + { + APPEND, + PREPEND, + TOLOWER, + TOUPPER, + STRIP, + GENEX_STRIP, + REPLACE + }; + + // Transforms the list by applying an action + // Throw std::transform_error is any of arguments specified are invalid + cmList& transform(TransformAction action, + std::unique_ptr<TransformSelector> = {}); + cmList& transform(TransformAction action, std::string const& arg, + std::unique_ptr<TransformSelector> = {}); + cmList& transform(TransformAction action, std::string const& arg1, + std::string const& arg2, + std::unique_ptr<TransformSelector> = {}); + cmList& transform(TransformAction action, + std::vector<std::string> const& args, + std::unique_ptr<TransformSelector> = {}); + + std::string join(cm::string_view glue) const; + + void swap(cmList& other) noexcept { this->Values.swap(other.Values); } + + // static members + // ============== + // these methods can be used to store CMake list expansion directly in a + // std::vector. + static void assign(cm::string_view value, + std::vector<std::string>& container, + EmptyElements emptyElements = EmptyElements::No) + { + container.clear(); + cmList::append(value, container, emptyElements); + } + static void assign(cmValue value, std::vector<std::string>& container, + EmptyElements emptyElements = EmptyElements::No) + { + if (value) { + cmList::assign(*value, container, emptyElements); + } else { + container.clear(); + } + } + template <typename InputIterator> + static void assign(InputIterator first, InputIterator last, + std::vector<std::string>& container, + EmptyElements emptyElements = EmptyElements::No) + { + container.clear(); + cmList::append(first, last, container, emptyElements); + } + + static std::vector<std::string>::iterator insert( + std::vector<std::string>::const_iterator pos, cm::string_view value, + std::vector<std::string>& container, + EmptyElements emptyElements = EmptyElements::No) + { + return cmList::Insert(pos, std::string(value), container, + ExpandElements::Yes, emptyElements); + } + static std::vector<std::string>::iterator insert( + std::vector<std::string>::const_iterator pos, cmValue value, + std::vector<std::string>& container, + EmptyElements emptyElements = EmptyElements::No) + { + if (value) { + return cmList::insert(pos, *value, container, emptyElements); + } + + auto delta = std::distance(container.cbegin(), pos); + return container.begin() + delta; + } + template <typename InputIterator> + static std::vector<std::string>::iterator insert( + std::vector<std::string>::const_iterator pos, InputIterator first, + InputIterator last, std::vector<std::string>& container, + EmptyElements emptyElements = EmptyElements::No) + { + return cmList::Insert(pos, first, last, container, ExpandElements::Yes, + emptyElements); + } + + static std::vector<std::string>::iterator append( + cm::string_view value, std::vector<std::string>& container, + EmptyElements emptyElements = EmptyElements::No) + { + return cmList::insert(container.cend(), value, container, emptyElements); + } + static std::vector<std::string>::iterator append( + cmValue value, std::vector<std::string>& container, + EmptyElements emptyElements = EmptyElements::No) + { + if (value) { + return cmList::append(*value, container, emptyElements); + } + + return container.end(); + } + template <typename InputIterator> + static std::vector<std::string>::iterator append( + InputIterator first, InputIterator last, + std::vector<std::string>& container, + EmptyElements emptyElements = EmptyElements::No) + { + return cmList::insert(container.cend(), first, last, container, + emptyElements); + } + + static std::vector<std::string>::iterator prepend( + cm::string_view value, std::vector<std::string>& container, + EmptyElements emptyElements = EmptyElements::No) + { + return cmList::insert(container.cbegin(), value, container, emptyElements); + } + static std::vector<std::string>::iterator prepend( + cmValue value, std::vector<std::string>& container, + EmptyElements emptyElements = EmptyElements::No) + { + if (value) { + return cmList::prepend(*value, container, emptyElements); + } + + return container.begin(); + } + template <typename InputIterator> + static std::vector<std::string>::iterator prepend( + InputIterator first, InputIterator last, + std::vector<std::string>& container, + EmptyElements emptyElements = EmptyElements::No) + { + return cmList::insert(container.cbegin(), first, last, container, + emptyElements); + } + + // The following methods offer the possibility to extend a CMake list + // but without any intermediate expansion. So the operation is simply a + // string concatenation with special handling for the CMake list item + // separator + static std::string& append(cm::string_view value, std::string& list); + template <typename InputIterator> + static std::string& append(InputIterator first, InputIterator last, + std::string& list) + { + if (first == last) { + return list; + } + + return cmList::append(cm::string_view{ std::accumulate( + std::next(first), last, *first, + [](std::string a, const std::string& b) { + return std::move(a) + + std::string(cmList::element_separator) + b; + }) }, + list); + } + + static std::string& prepend(cm::string_view value, std::string& list); + template <typename InputIterator> + static std::string& prepend(InputIterator first, InputIterator last, + std::string& list) + { + if (first == last) { + return list; + } + + return cmList::prepend(cm::string_view{ std::accumulate( + std::next(first), last, *first, + [](std::string a, const std::string& b) { + return std::move(a) + + std::string(cmList::element_separator) + b; + }) }, + list); + } + + // Non-members + // =========== + friend inline bool operator==(const cmList& lhs, const cmList& rhs) noexcept + { + return lhs.Values == rhs.Values; + } + friend inline bool operator!=(const cmList& lhs, const cmList& rhs) noexcept + { + return lhs.Values != rhs.Values; + } + +private: + size_type ComputeIndex(index_type pos, bool boundCheck = true) const; + size_type ComputeInsertIndex(index_type pos, bool boundCheck = true) const; + + cmList GetItems(std::vector<index_type>&& indexes) const; + + cmList& RemoveItems(std::vector<index_type>&& indexes); + cmList& RemoveItems(std::vector<std::string>&& items); + + static container_type::iterator Insert(container_type::const_iterator pos, + std::string&& value, + container_type& container, + ExpandElements expandElements, + EmptyElements emptyElements); + static container_type::iterator Insert(container_type::const_iterator pos, + const std::string& value, + container_type& container, + ExpandElements expandElements, + EmptyElements emptyElements) + { + auto tmp = value; + return cmList::Insert(pos, std::move(tmp), container, expandElements, + emptyElements); + } + template <typename InputIterator> + static container_type::iterator Insert(container_type::const_iterator pos, + InputIterator first, + InputIterator last, + container_type& container, + ExpandElements expandElements, + EmptyElements emptyElements) + { + auto delta = std::distance(container.cbegin(), pos); + + if (first == last) { + return container.begin() + delta; + } + + auto insertPos = container.begin() + delta; + if (expandElements == ExpandElements::Yes) { + for (; first != last; ++first) { + auto size = container.size(); + insertPos = cmList::Insert(insertPos, *first, container, + expandElements, emptyElements); + insertPos += container.size() - size; + } + } else { + for (; first != last; ++first) { + if (!first->empty() || emptyElements == EmptyElements::Yes) { + insertPos = container.insert(insertPos, *first); + insertPos++; + } + } + } + + return container.begin() + delta; + } + + container_type Values; +}; + +// specializations for cmList::TransformSelector allocators +// ======================================================== +template <> +inline std::unique_ptr<cmList::TransformSelector> +cmList::TransformSelector::New<cmList::TransformSelector::AT>( + std::initializer_list<index_type> init) +{ + return cmList::TransformSelector::NewAT(init); +} +template <> +inline std::unique_ptr<cmList::TransformSelector> +cmList::TransformSelector::New<cmList::TransformSelector::AT>( + std::vector<index_type> const& init) +{ + return cmList::TransformSelector::NewAT(init); +} +template <> +inline std::unique_ptr<cmList::TransformSelector> +cmList::TransformSelector::New<cmList::TransformSelector::AT>( + std::vector<index_type>&& init) +{ + return cmList::TransformSelector::NewAT(std::move(init)); +} + +template <> +inline std::unique_ptr<cmList::TransformSelector> +cmList::TransformSelector::New<cmList::TransformSelector::FOR>( + std::initializer_list<index_type> init) +{ + return cmList::TransformSelector::NewFOR(init); +} +template <> +inline std::unique_ptr<cmList::TransformSelector> +cmList::TransformSelector::New<cmList::TransformSelector::FOR>( + std::vector<index_type> const& init) +{ + return cmList::TransformSelector::NewFOR(init); +} +template <> +inline std::unique_ptr<cmList::TransformSelector> +cmList::TransformSelector::New<cmList::TransformSelector::FOR>( + std::vector<index_type>&& init) +{ + return cmList::TransformSelector::NewFOR(std::move(init)); +} + +template <> +inline std::unique_ptr<cmList::TransformSelector> +cmList::TransformSelector::New<cmList::TransformSelector::REGEX>( + std::string const& init) +{ + return cmList::TransformSelector::NewREGEX(init); +} +template <> +inline std::unique_ptr<cmList::TransformSelector> +cmList::TransformSelector::New<cmList::TransformSelector::REGEX>( + std::string&& init) +{ + return cmList::TransformSelector::NewREGEX(std::move(init)); +} + +// Non-member functions +// ==================== +inline std::vector<std::string>& operator+=(std::vector<std::string>& l, + const cmList& r) +{ + l.insert(l.end(), r.begin(), r.end()); + return l; +} +inline std::vector<std::string>& operator+=(std::vector<std::string>& l, + cmList&& r) +{ + std::move(r.begin(), r.end(), std::back_inserter(l)); + r.clear(); + + return l; +} + +namespace cm { +inline void erase(cmList& list, const std::string& value) +{ + list.erase(std::remove(list.begin(), list.end(), value), list.end()); +} + +template <typename Predicate> +inline void erase_if(cmList& list, Predicate pred) +{ + list.erase(std::remove_if(list.begin(), list.end(), pred), list.end()); +} +} + +namespace srd { +inline void swap(cmList& lhs, cmList& rhs) noexcept +{ + lhs.swap(rhs); +} +} diff --git a/Tests/CMakeLib/CMakeLists.txt b/Tests/CMakeLib/CMakeLists.txt index 612d4b4..0fc3deb 100644 --- a/Tests/CMakeLib/CMakeLists.txt +++ b/Tests/CMakeLib/CMakeLists.txt @@ -30,6 +30,7 @@ set(CMakeLib_TESTS testCMExtMemory.cxx testCMExtAlgorithm.cxx testCMExtEnumSet.cxx + testList.cxx ) if (CMake_TEST_FILESYSTEM_PATH OR NOT CMake_HAVE_CXX_FILESYSTEM) list(APPEND CMakeLib_TESTS testCMFilesystemPath.cxx) diff --git a/Tests/CMakeLib/testList.cxx b/Tests/CMakeLib/testList.cxx new file mode 100644 index 0000000..7294be0 --- /dev/null +++ b/Tests/CMakeLib/testList.cxx @@ -0,0 +1,995 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include <iostream> +#include <stdexcept> +#include <string> +#include <type_traits> +#include <utility> +#include <vector> + +#include <cm/string_view> + +#include "cmList.h" + +namespace { + +void checkResult(bool success) +{ + if (!success) { + std::cout << " => failed"; + } + std::cout << std::endl; +} + +bool testConstructors() +{ + std::cout << "testConstructors()"; + + bool result = true; + + { + cmList list; + if (!list.empty() || list != cmList{}) { + result = false; + } + } + { + cmList list{ "aa;bb" }; + if (list.size() != 2 || list.to_string() != "aa;bb") { + result = false; + } + } + { + cmList list1{ "aa", "bb" }; + cmList list2("aa;bb"); + + if (list1.size() != 2 || list2.size() != 2 || list1 != list2) { + result = false; + } + if (list1.to_string() != "aa;bb") { + result = false; + } + if (list1.to_string() != list2.to_string()) { + result = false; + } + } + { + std::vector<std::string> v{ "aa", "bb", "cc" }; + cmList list(v.begin(), v.end()); + if (list.size() != 3 || list.to_string() != "aa;bb;cc") { + result = false; + } + } + { + std::vector<std::string> values{ "aa;bb", "cc", "dd;ee" }; + cmList list1(values.begin(), values.end()); + cmList list2(values.begin(), values.end(), cmList::ExpandElements::No); + + if (list1.size() != 5 || list1.to_string() != "aa;bb;cc;dd;ee") { + result = false; + } + if (list2.size() != 3 || list2.to_string() != "aa;bb;cc;dd;ee") { + result = false; + } + } + { + std::vector<std::string> values{ "aa;bb;;cc", "", "dd;ee" }; + cmList list1(values.begin(), values.end(), cmList::ExpandElements::No, + cmList::EmptyElements::No); + cmList list2(values.begin(), values.end(), cmList::ExpandElements::No, + cmList::EmptyElements::Yes); + cmList list3(values.begin(), values.end(), cmList::ExpandElements::Yes, + cmList::EmptyElements::No); + cmList list4(values.begin(), values.end(), cmList::ExpandElements::Yes, + cmList::EmptyElements::Yes); + + if (list1.size() != 2 || list1.to_string() != "aa;bb;;cc;dd;ee") { + result = false; + } + if (list2.size() != 3 || list2.to_string() != "aa;bb;;cc;;dd;ee") { + result = false; + } + if (list3.size() != 5 || list3.to_string() != "aa;bb;cc;dd;ee") { + result = false; + } + if (list4.size() != 7 || list4.to_string() != "aa;bb;;cc;;dd;ee") { + result = false; + } + } + { + std::vector<std::string> values{ "aa;bb", "cc", "dd;ee" }; + cmList list1(values); + cmList list2(values, cmList::ExpandElements::No); + + if (list1.size() != 5 || list1.to_string() != "aa;bb;cc;dd;ee") { + result = false; + } + if (list2.size() != 3 || list2.to_string() != "aa;bb;cc;dd;ee") { + result = false; + } + } + { + std::vector<std::string> values{ "aa", "bb", "cc", "dd", "ee" }; + cmList list(std::move(values)); + + if (list.size() != 5 || list.to_string() != "aa;bb;cc;dd;ee") { + result = false; + } + if (!values.empty()) { + result = false; + } + } + + checkResult(result); + + return result; +} + +bool testAssign() +{ + std::cout << "testAssign()"; + + bool result = true; + + { + cmList list1{ "aa", "bb" }; + cmList list2{ "cc", "dd" }; + + list2 = list1; + if (list1.size() != 2 || list2.size() != 2 || list1 != list2) { + result = false; + } + if (list1.to_string() != "aa;bb") { + result = false; + } + if (list1.to_string() != list2.to_string()) { + result = false; + } + } + { + cmList list1{ "aa", "bb" }; + cmList list2{ "cc", "dd" }; + + list2 = std::move(list1); + if (!list1.empty() || list2.size() != 2) { + result = false; + } + if (list2.to_string() != "aa;bb") { + result = false; + } + } + { + std::vector<std::string> v{ "aa", "bb" }; + cmList list{ "cc", "dd" }; + + list = std::move(v); + if (!v.empty() || list.size() != 2) { + result = false; + } + if (list.to_string() != "aa;bb") { + result = false; + } + } + { + cmList list{ "cc", "dd" }; + + list = "aa;bb"; + if (list.size() != 2) { + result = false; + } + if (list.to_string() != "aa;bb") { + result = false; + } + } + + checkResult(result); + + return result; +} + +bool testConversions() +{ + std::cout << "testConversions()"; + + bool result = true; + + { + cmList list("a;b;c"); + std::string s = list.to_string(); + + if (s != "a;b;c") { + result = false; + } + } + { + cmList list("a;b;c"); + std::vector<std::string> v = list; + + if (list.size() != 3 || v.size() != 3) { + result = false; + } + } + { + cmList list("a;b;c"); + std::vector<std::string> v = std::move(list); + + // Microsoft compiler is not able to handle correctly the move semantics + // so the initial list is not moved, so do not check its size... + if (v.size() != 3) { + result = false; + } + } + { + cmList list("a;b;c"); + std::vector<std::string> v; + + // compiler is not able to select the cmList conversion operator + // and the std::vector assignment operator using the move semantics + // v = std::move(list); + v = std::move(list.data()); + + if (!list.empty() || v.size() != 3) { + result = false; + } + } + + checkResult(result); + + return result; +} + +bool testAccess() +{ + std::cout << "testAccess()"; + + bool result = true; + + { + cmList list{ "a", "b", "c" }; + if (list.at(1) != "b") { + result = false; + } + } + { + cmList list{ "a", "b", "c" }; + if (list.at(-3) != "a") { + result = false; + } + } + { + try { + cmList list{ "a", "b", "c" }; + if (list.at(4) != "a") { + result = false; + } + } catch (std::out_of_range&) { + } + } + { + try { + cmList list{ "a", "b", "c" }; + if (list.at(-4) != "a") { + result = false; + } + } catch (std::out_of_range&) { + } + } + { + cmList list{ "a", "b", "c", "d", "e" }; + auto sublist = list.sublist(list.begin() + 1, list.begin() + 3); + if (sublist.size() != 2 || sublist != cmList{ "b", "c" }) { + result = false; + } + } + { + cmList list{ "a", "b", "c", "d", "e" }; + auto sublist = list.sublist(1, 2); + if (sublist.size() != 2 || sublist != cmList{ "b", "c" }) { + result = false; + } + + sublist = list.sublist(1, cmList::npos); + if (sublist.size() != 4 || sublist != cmList{ "b", "c", "d", "e" }) { + result = false; + } + } + { + cmList list{ "a", "b", "c", "d", "e", "f" }; + auto sublist = list.get_items({ 1, 3, 5 }); + if (sublist.size() != 3 || sublist != cmList{ "b", "d", "f" }) { + result = false; + } + } + { + cmList list{ "a", "b", "c", "d", "e", "f" }; + auto sublist = list.get_items({ 1, -3, 5, -3 }); + if (sublist.size() != 4 || sublist != cmList{ "b", "d", "f", "d" }) { + result = false; + } + } + { + cmList list{ "a", "b", "c", "d", "e", "f" }; + try { + if (list.get_items({ 1, -3, 5, -3, 10 }).size() != 5) { + result = false; + } + } catch (std::out_of_range&) { + } + } + { + cmList list{ "a", "b", "c", "d", "e", "f" }; + + if (list.find("b") != 1) { + result = false; + } + if (list.find("x") != cmList::npos) { + result = false; + } + } + + checkResult(result); + + return result; +} + +bool testModifiers() +{ + std::cout << "testModifiers()"; + + bool result = true; + + { + cmList list{ "1;2;3;4;5" }; + + auto it = list.insert(list.begin() + 2, "6;7;8"); + if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") { + result = false; + } + if (*it != "6") { + result = false; + } + } + { + cmList list{ "1;2;3;4;5" }; + + auto it = + list.insert(list.begin() + 2, "6;7;8", cmList::ExpandElements::No); + if (list.size() != 6 || list.to_string() != "1;2;6;7;8;3;4;5") { + result = false; + } + if (*it != "6;7;8") { + result = false; + } + } + { + cmList list{ "1;2;3;4;5" }; + cmList v{ "6", "7", "8" }; + + auto it = list.insert(list.begin() + 2, v); + if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") { + result = false; + } + if (*it != "6") { + result = false; + } + } + { + cmList list{ "1;2;3;4;5" }; + cmList v{ "6", "7", "8" }; + + auto it = list.insert(list.begin() + 2, std::move(v)); + if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") { + result = false; + } + if (*it != "6") { + result = false; + } + + if (!v.empty()) { + result = false; + } + } + { + cmList list{ "1;2;3;4;5" }; + std::vector<std::string> v{ "6", "7", "8" }; + + auto it = list.insert(list.begin() + 2, v); + if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") { + result = false; + } + if (*it != "6") { + result = false; + } + } + { + cmList list{ "1;2;3;4;5" }; + std::vector<std::string> v{ "6;7", "8" }; + + auto it = list.insert(list.begin() + 2, v); + if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") { + result = false; + } + if (*it != "6") { + result = false; + } + } + { + cmList list{ "1;2;3;4;5" }; + std::vector<std::string> v{ "6;7", "8" }; + + auto it = list.insert(list.begin() + 2, v, cmList::ExpandElements::No); + if (list.size() != 7 || list.to_string() != "1;2;6;7;8;3;4;5") { + result = false; + } + if (*it != "6;7") { + result = false; + } + } + { + cmList list{ "1;2;3;4;5" }; + std::vector<std::string> v{ "6;;7", "8" }; + + auto it = list.insert(list.begin() + 2, v); + if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") { + result = false; + } + if (*it != "6") { + result = false; + } + } + { + cmList list{ "1;2;3;4;5" }; + std::vector<std::string> v{ "6;;7", "8" }; + + auto it = list.insert(list.begin() + 2, v, cmList::EmptyElements::Yes); + if (list.size() != 9 || list.to_string() != "1;2;6;;7;8;3;4;5") { + result = false; + } + if (*it != "6") { + result = false; + } + } + { + cmList list{ "1;2;3;4;5" }; + std::vector<std::string> v{ "6", "7", "8" }; + + auto it = list.insert(list.begin() + 2, std::move(v)); + if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") { + result = false; + } + if (*it != "6") { + result = false; + } + + if (!v.empty()) { + result = false; + } + } + + checkResult(result); + + return result; +} + +bool testRemoveItems() +{ + std::cout << "testRemoveItems()"; + + bool result = true; + + { + cmList list("a;b;c;d;e;f;g;h"); + + list.remove_items({ 1, 3, 5 }); + + if (list.size() != 5 || list.to_string() != "a;c;e;g;h") { + result = false; + } + } + { + cmList list("a;b;c;b;a;d;e;f"); + + list.remove_items({ "a", "b", "h" }); + + if (list.size() != 4 || list.to_string() != "c;d;e;f") { + result = false; + } + } + { + cmList list("a;b;c;d;e;f;g;h"); + std::vector<cmList::index_type> remove{ 1, 3, 5 }; + + list.remove_items(remove.begin(), remove.end()); + + if (list.size() != 5 || list.to_string() != "a;c;e;g;h") { + result = false; + } + } + { + cmList list("a;b;c;b;a;d;e;f"); + std::vector<std::string> remove{ "b", "a", "h" }; + + list.remove_items(remove.begin(), remove.end()); + + if (list.size() != 4 || list.to_string() != "c;d;e;f") { + result = false; + } + } + + checkResult(result); + + return result; +} + +bool testRemoveDuplicates() +{ + std::cout << "testRemoveDuplicates()"; + + bool result = true; + + { + cmList list("b;c;b;a;a;c;b;a;c;b"); + + list.remove_duplicates(); + + if (list.size() != 3 || list.to_string() != "b;c;a") { + result = false; + } + } + + checkResult(result); + + return result; +} + +bool testFilter() +{ + std::cout << "testFilter()"; + + bool result = true; + + { + cmList list{ "AA", "Aa", "aA" }; + + list.filter("^A", cmList::FilterMode::INCLUDE); + if (list.size() != 2 || list.to_string() != "AA;Aa") { + result = false; + } + } + { + cmList list{ "AA", "Aa", "aA" }; + + list.filter("^A", cmList::FilterMode::EXCLUDE); + if (list.size() != 1 || list.to_string() != "aA") { + result = false; + } + } + { + cmList list{ "AA", "Aa", "aA" }; + + try { + list.filter("^(A", cmList::FilterMode::EXCLUDE); + if (list.size() != 1) { + result = false; + } + } catch (const std::invalid_argument&) { + } + } + + checkResult(result); + + return result; +} + +bool testReverse() +{ + std::cout << "testReverse()"; + + bool result = true; + + { + cmList list{ "a", "b", "c" }; + if (list.reverse().to_string() != "c;b;a") { + result = false; + } + } + + checkResult(result); + + return result; +} + +bool testSort() +{ + std::cout << "testSort()"; + + bool result = true; + + using SortConfiguration = cmList::SortConfiguration; + + { + cmList list{ "A", "D", "C", "B", "A" }; + + list.sort(); + if (list.to_string() != "A;A;B;C;D") { + result = false; + } + + list.sort({ SortConfiguration::OrderMode::DESCENDING, + SortConfiguration::CompareMethod::DEFAULT, + SortConfiguration::CaseSensitivity::DEFAULT }); + if (list.to_string() != "D;C;B;A;A") { + result = false; + } + } + { + SortConfiguration sortCfg; + cmList list{ "1.0", "1.1", "2.5", "10.2" }; + + list.sort(sortCfg); + if (list.to_string() != "1.0;1.1;10.2;2.5") { + result = false; + } + + sortCfg.Compare = SortConfiguration::CompareMethod::NATURAL; + list.sort(sortCfg); + if (list.to_string() != "1.0;1.1;2.5;10.2") { + result = false; + } + + sortCfg.Order = SortConfiguration::OrderMode::DESCENDING; + list.sort(sortCfg); + if (list.to_string() != "10.2;2.5;1.1;1.0") { + result = false; + } + } + { + SortConfiguration sortCfg; + cmList list{ "/zz/bb.cc", "/xx/yy/dd.cc", "/aa/cc.aa" }; + + list.sort(sortCfg); + if (list.to_string() != "/aa/cc.aa;/xx/yy/dd.cc;/zz/bb.cc") { + result = false; + } + + sortCfg.Compare = SortConfiguration::CompareMethod::FILE_BASENAME; + if (list.sort(sortCfg).to_string() != "/zz/bb.cc;/aa/cc.aa;/xx/yy/dd.cc") { + result = false; + } + } + { + SortConfiguration sortCfg; + cmList list{ "c/B", "a/c", "B/a" }; + + if (list.sort().to_string() != "B/a;a/c;c/B") { + result = false; + } + + sortCfg.Case = SortConfiguration::CaseSensitivity::INSENSITIVE; + if (list.sort(sortCfg).to_string() != "a/c;B/a;c/B") { + result = false; + } + } + + checkResult(result); + + return result; +} + +bool testTransform() +{ + std::cout << "testTransform()"; + + bool result = true; + + using AT = cmList::TransformSelector::AT; + using FOR = cmList::TransformSelector::FOR; + using REGEX = cmList::TransformSelector::REGEX; + + { + cmList list({ "AA", "BB", "CC", "DD", "EE" }); + + list.transform(cmList::TransformAction::APPEND, "-X"); + if (list.to_string() != "AA-X;BB-X;CC-X;DD-X;EE-X") { + result = false; + } + } + { + cmList list({ "AA", "BB", "CC", "DD", "EE" }); + + list.transform(cmList::TransformAction::PREPEND, "X-"); + if (list.to_string() != "X-AA;X-BB;X-CC;X-DD;X-EE") { + result = false; + } + } + { + cmList list({ "AA", "BB", "CC", "DD", "EE" }); + + list.transform(cmList::TransformAction::TOLOWER); + if (list.to_string() != "aa;bb;cc;dd;ee") { + result = false; + } + } + { + cmList list({ "aa", "bb", "cc", "dd", "ee" }); + + list.transform(cmList::TransformAction::TOUPPER); + if (list.to_string() != "AA;BB;CC;DD;EE") { + result = false; + } + } + { + cmList list({ " AA", "BB ", " CC ", "DD", "EE" }); + + list.transform(cmList::TransformAction::STRIP); + if (list.to_string() != "AA;BB;CC;DD;EE") { + result = false; + } + } + { + cmList list({ "$<CONFIG>AA", "BB$<OR>", "C$<AND>C", "$<OR>DD$<AND>", + "$<>E$<>E$<>" }); + + list.transform(cmList::TransformAction::GENEX_STRIP); + if (list.to_string() != "AA;BB;CC;DD;EE") { + result = false; + } + } + { + cmList list({ "ABC", "BBCB", "BCCCBC", "BCBCDD", "EBCBCEBC" }); + + list.transform(cmList::TransformAction::REPLACE, "^BC|BC$", "X"); + if (list.to_string() != "AX;BBCB;XCCX;XXDD;EBCBCEX") { + result = false; + } + } + { + auto atSelector = cmList::TransformSelector::New<AT>({ 1, 2, 4 }); + cmList list({ "AA", "BB", "CC", "DD", "EE" }); + + list.transform(cmList::TransformAction::TOLOWER, std::move(atSelector)); + if (list.to_string() != "AA;bb;cc;DD;ee") { + result = false; + } + } + { + auto atSelector = cmList::TransformSelector::New<AT>({ 1, 2, -1 }); + cmList list({ "AA", "BB", "CC", "DD", "EE" }); + + list.transform(cmList::TransformAction::TOLOWER, std::move(atSelector)); + if (list.to_string() != "AA;bb;cc;DD;ee") { + result = false; + } + } + { + auto forSelector = cmList::TransformSelector::New<FOR>({ 1, 3 }); + cmList list({ "AA", "BB", "CC", "DD", "EE" }); + + list.transform(cmList::TransformAction::TOLOWER, std::move(forSelector)); + if (list.to_string() != "AA;bb;cc;dd;EE") { + result = false; + } + } + { + auto forSelector = cmList::TransformSelector::New<FOR>({ 0, 4, 2 }); + cmList list({ "AA", "BB", "CC", "DD", "EE" }); + + list.transform(cmList::TransformAction::TOLOWER, std::move(forSelector)); + if (list.to_string() != "aa;BB;cc;DD;ee") { + result = false; + } + } + { + auto regexSelector = cmList::TransformSelector::New<REGEX>("^(A|D|E)"); + cmList list({ "AA", "BB", "CC", "DD", "EE" }); + + list.transform(cmList::TransformAction::TOLOWER, std::move(regexSelector)); + if (list.to_string() != "aa;BB;CC;dd;ee") { + result = false; + } + } + + checkResult(result); + + return result; +} + +bool testStaticModifiers() +{ + std::cout << "testStaticModifiers()"; + + bool result = true; + + { + std::vector<std::string> v{ "a", "b", "c" }; + cmList::assign("d;e", v); + + if (v.size() != 2 || v[0] != "d" || v[1] != "e") { + result = false; + } + } + { + std::vector<std::string> v{ "a", "b", "c" }; + cmList::append("d;;e", v); + + if (v.size() != 5 || v[3] != "d" || v[4] != "e") { + result = false; + } + } + { + std::vector<std::string> v{ "a", "b", "c" }; + cmList::append("d;;e", v, cmList::EmptyElements::Yes); + + if (v.size() != 6 || v[3] != "d" || !v[4].empty() || v[5] != "e") { + result = false; + } + } + { + std::vector<std::string> v{ "a", "b", "c" }; + cmList::prepend("d;e", v); + + if (v.size() != 5 || v[0] != "d" || v[1] != "e") { + result = false; + } + } + { + std::vector<std::string> v{ "a", "b", "c" }; + cmList::prepend("d;;e", v, cmList::EmptyElements::Yes); + + if (v.size() != 6 || v[0] != "d" || !v[1].empty() || v[2] != "e") { + result = false; + } + } + { + std::string list{ "a;b;c" }; + cmList::append("d;e", list); + + if (list != "a;b;c;d;e") { + result = false; + } + } + { + std::string list; + cmList::append("d;e", list); + + if (list != "d;e") { + result = false; + } + } + { + std::string list{ "a;b;c" }; + cmList::append("", list); + + if (list != "a;b;c;") { + result = false; + } + } + { + std::string list{ "a;b;c" }; + std::vector<std::string> v{ "d", "e" }; + cmList::append(v.begin(), v.end(), list); + + if (list != "a;b;c;d;e") { + result = false; + } + } + { + std::string list{ "a;b;c" }; + std::vector<std::string> v; + cmList::append(v.begin(), v.end(), list); + + if (list != "a;b;c") { + result = false; + } + } + { + std::string list; + std::vector<std::string> v{ "d", "e" }; + cmList::append(v.begin(), v.end(), list); + + if (list != "d;e") { + result = false; + } + } + { + std::string list{ "a;b;c" }; + cmList::prepend("d;e", list); + + if (list != "d;e;a;b;c") { + result = false; + } + } + { + std::string list; + cmList::prepend("d;e", list); + + if (list != "d;e") { + result = false; + } + } + { + std::string list{ "a;b;c" }; + cmList::prepend("", list); + + if (list != ";a;b;c") { + result = false; + } + } + { + std::string list{ "a;b;c" }; + std::vector<std::string> v{ "d", "e" }; + cmList::prepend(v.begin(), v.end(), list); + + if (list != "d;e;a;b;c") { + result = false; + } + } + { + std::string list{ "a;b;c" }; + std::vector<std::string> v; + cmList::prepend(v.begin(), v.end(), list); + + if (list != "a;b;c") { + result = false; + } + } + { + std::string list; + std::vector<std::string> v{ "d", "e" }; + cmList::prepend(v.begin(), v.end(), list); + + if (list != "d;e") { + result = false; + } + } + + checkResult(result); + + return result; +} +} + +int testList(int /*unused*/, char* /*unused*/[]) +{ + int result = 0; + + if (!testConstructors()) { + result = 1; + } + if (!testAssign()) { + result = 1; + } + if (!testConversions()) { + result = 1; + } + if (!testAccess()) { + result = 1; + } + if (!testModifiers()) { + result = 1; + } + if (!testRemoveItems()) { + result = 1; + } + if (!testRemoveDuplicates()) { + result = 1; + } + if (!testFilter()) { + result = 1; + } + if (!testReverse()) { + result = 1; + } + if (!testSort()) { + result = 1; + } + if (!testTransform()) { + result = 1; + } + if (!testStaticModifiers()) { + result = 1; + } + + return result; +} @@ -307,6 +307,7 @@ CMAKE_CXX_SOURCES="\ cmBuildCommand \ cmCMakeLanguageCommand \ cmCMakeMinimumRequired \ + cmList \ cmCMakePath \ cmCMakePathCommand \ cmCMakePolicyCommand \ |