/* 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 #include #include #include #include #include #include #include #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("sub-command FILTER, mode REGEX failed to compile 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; 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; ComparisonFunction GetComparisonFunction(CompareMethod compare) { if (compare == CompareMethod::NATURAL) { return std::function( [](const std::string& x, const std::string& y) { return cmSystemTools::strverscmp(x, y) < 0; }); } return std::function( [](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; 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 Indexes; bool InSelection(const std::string&) override { return true; } void Transform(std::vector& 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 const& indexes) : TransformSelector(std::move(tag)) , Indexes(indexes) { } TransformSelectorIndexes(std::string&& tag, std::vector&& indexes) : TransformSelector(std::move(tag)) , Indexes(indexes) { } index_type NormalizeIndex(index_type index, std::size_t count) { if (index < 0) { index = static_cast(count) + index; } if (index < 0 || count <= static_cast(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 const& indexes) : TransformSelectorIndexes("AT", indexes) { } TransformSelectorAt(std::vector&& 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(index_type start, index_type stop, index_type 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 to be no greater than (", 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]() -> index_type { 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&) { 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& 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& 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(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& 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 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 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 Transform; }; // Build a set of supported actions. using ActionDescriptorSet = std::set< ActionDescriptor, std::function>; ActionDescriptorSet Descriptors([](cmList::TransformAction x, cmList::TransformAction y) { return x < y; }); ActionDescriptorSet::iterator TransformConfigure( cmList::TransformAction action, std::unique_ptr& selector, std::size_t arity) { if (Descriptors.empty()) { Descriptors.emplace(cmList::TransformAction::APPEND, "APPEND", 1, cm::make_unique()); Descriptors.emplace(cmList::TransformAction::PREPEND, "PREPEND", 1, cm::make_unique()); Descriptors.emplace(cmList::TransformAction::TOUPPER, "TOUPPER", 0, cm::make_unique()); Descriptors.emplace(cmList::TransformAction::TOLOWER, "TOLOWER", 0, cm::make_unique()); Descriptors.emplace(cmList::TransformAction::STRIP, "STRIP", 0, cm::make_unique()); Descriptors.emplace(cmList::TransformAction::GENEX_STRIP, "GENEX_STRIP", 0, cm::make_unique()); Descriptors.emplace(cmList::TransformAction::REPLACE, "REPLACE", 2, cm::make_unique()); } auto descriptor = Descriptors.find(action); if (descriptor == Descriptors.end()) { throw transform_error(cmStrCat(" sub-command TRANSFORM, ", std::to_string(static_cast(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(); } return descriptor; } } std::unique_ptr cmList::TransformSelector::NewAT( std::initializer_list indexes) { return cm::make_unique( std::vector{ indexes.begin(), indexes.end() }); ; } std::unique_ptr cmList::TransformSelector::NewAT( std::vector const& indexes) { return cm::make_unique(indexes); } std::unique_ptr cmList::TransformSelector::NewAT( std::vector&& indexes) { return cm::make_unique(std::move(indexes)); } std::unique_ptr cmList::TransformSelector::NewFOR( std::initializer_list 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 ."); } return cm::make_unique( *indexes.begin(), *(indexes.begin() + 1), indexes.size() == 3 ? *(indexes.begin() + 2) : 1); } std::unique_ptr cmList::TransformSelector::NewFOR( std::vector 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 ."); } return cm::make_unique( indexes[0], indexes[1], indexes.size() == 3 ? indexes[2] : 1); } std::unique_ptr cmList::TransformSelector::NewFOR( std::vector&& 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 ."); } return cm::make_unique( indexes[0], indexes[1], indexes.size() == 3 ? indexes[2] : 1); } std::unique_ptr cmList::TransformSelector::NewREGEX( std::string const& regex) { std::unique_ptr<::TransformSelector> selector = cm::make_unique(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(selector.release()); } std::unique_ptr cmList::TransformSelector::NewREGEX( std::string&& regex) { std::unique_ptr<::TransformSelector> selector = cm::make_unique(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(selector.release()); } cmList& cmList::transform(TransformAction action, std::unique_ptr 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 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 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 const& args, std::unique_ptr 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::append(std::string& list, std::string&& value) { if (list.empty()) { list = std::move(value); } else { list += cmStrCat(cmList::element_separator, value); } return list; } std::string& cmList::append(std::string& list, cm::string_view value) { return cmList::append(list, std::string{ value }); } std::string& cmList::prepend(std::string& list, std::string&& value) { if (list.empty()) { list = std::move(value); } else { list.insert(0, cmStrCat(value, cmList::element_separator)); } return list; } std::string& cmList::prepend(std::string& list, cm::string_view value) { return cmList::prepend(list, std::string{ value }); } 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)")); } auto index = pos; if (!this->Values.empty()) { auto length = this->Values.size(); if (index < 0) { index = static_cast(length) + index; } if (index < 0 || length <= static_cast(index)) { throw std::out_of_range(cmStrCat("index: ", pos, " out of range (-", this->Values.size(), ", ", this->Values.size() - 1, ")")); } } return index; } 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)")); } auto index = pos; if (!this->Values.empty()) { auto length = this->Values.size(); if (index < 0) { index = static_cast(length) + index; } if (index < 0 || length < static_cast(index)) { throw std::out_of_range(cmStrCat("index: ", pos, " out of range (-", this->Values.size(), ", ", this->Values.size(), ")")); } } return index; } return pos < 0 ? this->Values.size() + pos : pos; } cmList cmList::GetItems(std::vector&& indexes) const { cmList listItems; for (auto index : indexes) { listItems.emplace_back(this->get_item(index)); } return listItems; } cmList& cmList::RemoveItems(std::vector&& indexes) { if (indexes.empty()) { return *this; } // compute all indexes std::vector idx(indexes.size()); std::transform(indexes.cbegin(), indexes.cend(), idx.begin(), [this](const index_type& index) -> size_type { 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&& 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& container, container_type::const_iterator pos, std::string&& value, 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; }