mirror of
https://github.com/boostorg/url.git
synced 2026-01-19 04:42:15 +00:00
feat: segments_view iterator constructors
This commit is contained in:
163
include/boost/url/detail/segments_range.hpp
Normal file
163
include/boost/url/detail/segments_range.hpp
Normal file
@@ -0,0 +1,163 @@
|
||||
//
|
||||
// Copyright (c) 2025 Alan de Freitas (alandefreitas@gmail.com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
// Official repository: https://github.com/boostorg/url
|
||||
//
|
||||
|
||||
#ifndef BOOST_URL_DETAIL_SEGMENTS_RANGE_HPP
|
||||
#define BOOST_URL_DETAIL_SEGMENTS_RANGE_HPP
|
||||
|
||||
#include <boost/url/detail/config.hpp>
|
||||
#include <boost/url/detail/url_impl.hpp>
|
||||
#include <boost/url/segments_base.hpp>
|
||||
#include <boost/url/segments_encoded_base.hpp>
|
||||
#include <boost/core/detail/string_view.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace urls {
|
||||
namespace detail {
|
||||
|
||||
struct segments_iter_access
|
||||
{
|
||||
static
|
||||
segments_iter_impl const&
|
||||
impl(segments_base::iterator const& it) noexcept
|
||||
{
|
||||
return it.it_;
|
||||
}
|
||||
|
||||
static
|
||||
segments_iter_impl const&
|
||||
impl(segments_encoded_base::iterator const& it) noexcept
|
||||
{
|
||||
return it.it_;
|
||||
}
|
||||
};
|
||||
|
||||
inline
|
||||
path_ref
|
||||
make_subref_from_impls(
|
||||
segments_iter_impl const& first,
|
||||
segments_iter_impl const& last) noexcept
|
||||
{
|
||||
BOOST_ASSERT(first.ref.alias_of(last.ref));
|
||||
path_ref const& ref = first.ref;
|
||||
|
||||
std::size_t const i0 = first.index;
|
||||
std::size_t const i1 = last.index;
|
||||
BOOST_ASSERT(i0 <= i1);
|
||||
std::size_t const nseg = i1 - i0;
|
||||
|
||||
bool const absolute = ref.buffer().starts_with('/');
|
||||
|
||||
// Empty range
|
||||
if (nseg == 0)
|
||||
{
|
||||
std::size_t off0;
|
||||
if (i0 == 0)
|
||||
{
|
||||
// [begin, begin): don't include the leading '/'
|
||||
// for absolute, start right after the leading '/';
|
||||
if (absolute)
|
||||
{
|
||||
off0 = 1;
|
||||
}
|
||||
// for relative, start at the first segment character.
|
||||
else
|
||||
{
|
||||
off0 = first.pos;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// [it, it) in the middle:
|
||||
// skip the separator before segment i0
|
||||
off0 = first.pos + 1;
|
||||
}
|
||||
|
||||
core::string_view const sub(ref.data() + off0, 0);
|
||||
return {sub, 0, 0};
|
||||
}
|
||||
|
||||
// General case: non-empty range
|
||||
// Start offset
|
||||
std::size_t off0;
|
||||
bool include_leading_slash = false;
|
||||
if (i0 == 0)
|
||||
{
|
||||
if (absolute)
|
||||
{
|
||||
// include leading '/'
|
||||
off0 = 0;
|
||||
include_leading_slash = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// relative: start at first segment
|
||||
off0 = first.pos;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// skip slash before segment i0
|
||||
off0 = first.pos + 1;
|
||||
}
|
||||
|
||||
// End offset
|
||||
std::size_t off1;
|
||||
if(i1 == ref.nseg())
|
||||
{
|
||||
off1 = ref.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
// stop before the slash preceding i1
|
||||
off1 = last.pos;
|
||||
}
|
||||
|
||||
BOOST_ASSERT(off1 >= off0);
|
||||
core::string_view const sub(ref.data() + off0, off1 - off0);
|
||||
|
||||
// Decoded-length:
|
||||
// sum per-segment decoded lengths + internal '/' + the leading '/'.
|
||||
std::size_t dn_sum = 0;
|
||||
{
|
||||
// copy to iterate
|
||||
segments_iter_impl cur = first;
|
||||
for (std::size_t k = 0; k < nseg; ++k)
|
||||
{
|
||||
// per-segment decoded length
|
||||
dn_sum += cur.dn;
|
||||
cur.increment();
|
||||
}
|
||||
// internal '/'s
|
||||
dn_sum += (nseg - 1);
|
||||
// leading '/'
|
||||
if (include_leading_slash)
|
||||
{
|
||||
++dn_sum;
|
||||
}
|
||||
}
|
||||
|
||||
return {sub, dn_sum, nseg};
|
||||
}
|
||||
|
||||
template<class Iter>
|
||||
inline
|
||||
path_ref
|
||||
make_subref(Iter const& first, Iter const& last) noexcept
|
||||
{
|
||||
auto const& f = segments_iter_access::impl(first);
|
||||
auto const& l = segments_iter_access::impl(last);
|
||||
return make_subref_from_impls(f, l);
|
||||
}
|
||||
|
||||
} // detail
|
||||
} // urls
|
||||
} // boost
|
||||
|
||||
#endif
|
||||
@@ -17,6 +17,9 @@
|
||||
|
||||
namespace boost {
|
||||
namespace urls {
|
||||
namespace detail {
|
||||
struct segments_iter_access;
|
||||
}
|
||||
|
||||
class segments_base::iterator
|
||||
{
|
||||
@@ -24,6 +27,7 @@ class segments_base::iterator
|
||||
|
||||
friend class segments_base;
|
||||
friend class segments_ref;
|
||||
friend struct detail::segments_iter_access;
|
||||
|
||||
iterator(detail::path_ref const&) noexcept;
|
||||
iterator(detail::path_ref const&, int) noexcept;
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
namespace boost {
|
||||
namespace urls {
|
||||
namespace detail {
|
||||
struct segments_iter_access;
|
||||
}
|
||||
|
||||
class segments_encoded_base::iterator
|
||||
{
|
||||
@@ -24,6 +27,7 @@ class segments_encoded_base::iterator
|
||||
friend class url_base;
|
||||
friend class segments_encoded_base;
|
||||
friend class segments_encoded_ref;
|
||||
friend struct detail::segments_iter_access;
|
||||
|
||||
iterator(detail::path_ref const&) noexcept;
|
||||
iterator(detail::path_ref const&, int) noexcept;
|
||||
|
||||
@@ -167,6 +167,53 @@ public:
|
||||
segments_encoded_view(
|
||||
core::string_view s);
|
||||
|
||||
/** Constructor
|
||||
|
||||
This function creates a new @ref segments_encoded_view
|
||||
from a pair of iterators referring to
|
||||
elements of another encoded segments
|
||||
view. The resulting view references
|
||||
the same underlying character buffer
|
||||
as the original.
|
||||
|
||||
The caller is responsible for ensuring
|
||||
that the lifetime of the original buffer
|
||||
extends until the constructed view
|
||||
is no longer referenced.
|
||||
|
||||
@par Example
|
||||
@code
|
||||
segments_encoded_view ps( "/path/to/file.txt" );
|
||||
|
||||
segments_encoded_view sub(
|
||||
std::next(ps.begin()),
|
||||
ps.end());
|
||||
|
||||
// sub represents "to/file.txt"
|
||||
@endcode
|
||||
|
||||
@par Preconditions
|
||||
The iterators must be valid and belong to
|
||||
the same @ref segments_encoded_view.
|
||||
|
||||
@par Postconditions
|
||||
`sub.buffer()` references characters in the
|
||||
original `ps.buffer()`.
|
||||
|
||||
@par Complexity
|
||||
Linear in `sub.buffer()`
|
||||
|
||||
@par Exception Safety
|
||||
Throws nothing.
|
||||
|
||||
@param first The beginning iterator.
|
||||
@param last The ending iterator.
|
||||
*/
|
||||
BOOST_URL_DECL
|
||||
segments_encoded_view(
|
||||
iterator first,
|
||||
iterator last) noexcept;
|
||||
|
||||
/** Assignment
|
||||
|
||||
After assignment, both views
|
||||
|
||||
@@ -165,6 +165,53 @@ public:
|
||||
segments_view(
|
||||
core::string_view s);
|
||||
|
||||
/** Constructor
|
||||
|
||||
This function creates a new @ref segments_view
|
||||
from a pair of iterators referring to
|
||||
elements of another segments view. The
|
||||
resulting view references the same
|
||||
underlying character buffer as the
|
||||
original.
|
||||
|
||||
The caller is responsible for ensuring
|
||||
that the lifetime of the original buffer
|
||||
extends until the constructed view is no
|
||||
longer referenced.
|
||||
|
||||
@par Example
|
||||
@code
|
||||
segments_view ps( "/path/to/file.txt" );
|
||||
|
||||
segments_view sub(
|
||||
std::next(ps.begin()),
|
||||
ps.end());
|
||||
|
||||
// sub represents "to/file.txt"
|
||||
@endcode
|
||||
|
||||
@par Preconditions
|
||||
The iterators must be valid and belong to
|
||||
the same @ref segments_view.
|
||||
|
||||
@par Postconditions
|
||||
`sub.buffer()` references characters in the
|
||||
original `ps.buffer()`.
|
||||
|
||||
@par Complexity
|
||||
Linear in `sub.buffer()`
|
||||
|
||||
@par Exception Safety
|
||||
Throws nothing.
|
||||
|
||||
@param first The beginning iterator.
|
||||
@param last The ending iterator.
|
||||
*/
|
||||
BOOST_URL_DECL
|
||||
segments_view(
|
||||
iterator first,
|
||||
iterator last) noexcept;
|
||||
|
||||
/** Assignment
|
||||
|
||||
After assignment, both views
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
|
||||
#include <boost/url/detail/config.hpp>
|
||||
#include <boost/url/detail/segments_range.hpp>
|
||||
#include <boost/url/segments_encoded_view.hpp>
|
||||
#include <boost/url/parse_path.hpp>
|
||||
|
||||
@@ -32,6 +33,12 @@ segments_encoded_view(
|
||||
{
|
||||
}
|
||||
|
||||
segments_encoded_view::
|
||||
segments_encoded_view(iterator first, iterator last) noexcept
|
||||
: segments_encoded_base(detail::make_subref(first, last))
|
||||
{
|
||||
}
|
||||
|
||||
segments_encoded_view::
|
||||
operator
|
||||
segments_view() const noexcept
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
|
||||
#include <boost/url/detail/config.hpp>
|
||||
#include <boost/url/detail/segments_range.hpp>
|
||||
#include <boost/url/segments_view.hpp>
|
||||
#include <boost/url/parse_path.hpp>
|
||||
|
||||
@@ -32,6 +33,12 @@ segments_view(
|
||||
{
|
||||
}
|
||||
|
||||
segments_view::
|
||||
segments_view(iterator first, iterator last) noexcept
|
||||
: segments_base(detail::make_subref(first, last))
|
||||
{
|
||||
}
|
||||
|
||||
} // urls
|
||||
} // boost
|
||||
|
||||
|
||||
@@ -95,6 +95,23 @@ struct segments_const_encoded_view_test
|
||||
BOOST_TEST_THROWS(segments_encoded_view("FA%"), system::system_error);
|
||||
}
|
||||
|
||||
// segments_encoded_view(iterator, iterator)
|
||||
{
|
||||
segments_encoded_view ps = parse_path("/a/b/c").value();
|
||||
auto first = std::next(ps.begin());
|
||||
auto last = ps.end();
|
||||
segments_encoded_view sub(first, last);
|
||||
|
||||
BOOST_TEST_EQ(sub.size(), 2u);
|
||||
BOOST_TEST(!sub.is_absolute());
|
||||
BOOST_TEST_EQ(sub.buffer(), "b/c");
|
||||
|
||||
auto it = sub.begin();
|
||||
BOOST_TEST_EQ(*it++, "b");
|
||||
BOOST_TEST_EQ(*it++, "c");
|
||||
BOOST_TEST(it == sub.end());
|
||||
}
|
||||
|
||||
// operator=(segments_encoded_view)
|
||||
{
|
||||
segments_encoded_view ps0("/path/to/file.txt");
|
||||
|
||||
@@ -116,6 +116,240 @@ struct segments_view_test
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testRangeCtor()
|
||||
{
|
||||
// full slice equals original (absolute path)
|
||||
{
|
||||
segments_view ps = parse_path("/a/b/c").value();
|
||||
segments_view sub(ps.begin(), ps.end());
|
||||
|
||||
BOOST_TEST_EQ(sub.size(), 3u);
|
||||
BOOST_TEST(sub.is_absolute());
|
||||
BOOST_TEST_EQ(sub.buffer(), "/a/b/c");
|
||||
// alias same storage
|
||||
BOOST_TEST_EQ(sub.buffer().data(), ps.buffer().data());
|
||||
}
|
||||
|
||||
// full slice equals original (relative path)
|
||||
{
|
||||
segments_view ps = parse_path("a/b/c").value();
|
||||
segments_view sub(ps.begin(), ps.end());
|
||||
|
||||
BOOST_TEST_EQ(sub.size(), 3u);
|
||||
BOOST_TEST(!sub.is_absolute());
|
||||
BOOST_TEST_EQ(sub.buffer(), "a/b/c");
|
||||
// alias same storage
|
||||
BOOST_TEST_EQ(sub.buffer().data(), ps.buffer().data());
|
||||
}
|
||||
|
||||
// drop first segment: start at index 1 (no leading slash)
|
||||
{
|
||||
segments_view ps = parse_path("/a/b/c").value();
|
||||
auto first = std::next(ps.begin());
|
||||
auto last = ps.end();
|
||||
segments_view sub(first, last);
|
||||
|
||||
BOOST_TEST_EQ(sub.size(), 2u);
|
||||
BOOST_TEST(!sub.is_absolute());
|
||||
BOOST_TEST_EQ(sub.buffer(), "b/c");
|
||||
|
||||
auto it = sub.begin();
|
||||
BOOST_TEST_EQ(*it++, "b");
|
||||
BOOST_TEST_EQ(*it++, "c");
|
||||
BOOST_TEST(it == sub.end());
|
||||
}
|
||||
|
||||
// take prefix without the last segment:
|
||||
// [begin, prev(end)) -> "/a/b"
|
||||
{
|
||||
segments_view ps = parse_path("/a/b/c").value();
|
||||
auto first = ps.begin();
|
||||
auto last = std::prev(ps.end());
|
||||
segments_view sub(first, last);
|
||||
|
||||
BOOST_TEST_EQ(sub.size(), 2u);
|
||||
BOOST_TEST(sub.is_absolute());
|
||||
BOOST_TEST_EQ(sub.buffer(), "/a/b");
|
||||
|
||||
auto it = sub.begin();
|
||||
BOOST_TEST_EQ(*it++, "a");
|
||||
BOOST_TEST_EQ(*it++, "b");
|
||||
BOOST_TEST(it == sub.end());
|
||||
}
|
||||
|
||||
// single segment in the middle:
|
||||
// ["b", past "b") -> "b"
|
||||
{
|
||||
segments_view ps = parse_path("/a/b/c").value();
|
||||
auto b = std::next(ps.begin());
|
||||
auto e = std::next(b);
|
||||
segments_view sub(b, e);
|
||||
|
||||
BOOST_TEST_EQ(sub.size(), 1u);
|
||||
BOOST_TEST(!sub.is_absolute());
|
||||
BOOST_TEST_EQ(sub.buffer(), "b");
|
||||
BOOST_TEST_EQ(*sub.begin(), "b");
|
||||
}
|
||||
|
||||
// relative path:
|
||||
// no leading slash preserved in any slice
|
||||
{
|
||||
segments_view ps = parse_path("a/b/c").value();
|
||||
|
||||
// full slice
|
||||
segments_view sub1(ps.begin(), ps.end());
|
||||
BOOST_TEST_EQ(sub1.size(), 3u);
|
||||
BOOST_TEST(!sub1.is_absolute());
|
||||
BOOST_TEST_EQ(sub1.buffer(), "a/b/c");
|
||||
|
||||
// prefix [begin, prev(end)) -> "a/b"
|
||||
segments_view sub2(ps.begin(), std::prev(ps.end()));
|
||||
BOOST_TEST_EQ(sub2.size(), 2u);
|
||||
BOOST_TEST(!sub2.is_absolute());
|
||||
BOOST_TEST_EQ(sub2.buffer(), "a/b");
|
||||
|
||||
// middle one ["b", past "b") -> "b"
|
||||
auto b = std::next(ps.begin());
|
||||
segments_view sub3(b, std::next(b));
|
||||
BOOST_TEST_EQ(sub3.size(), 1u);
|
||||
BOOST_TEST(!sub3.is_absolute());
|
||||
BOOST_TEST_EQ(sub3.buffer(), "b");
|
||||
}
|
||||
|
||||
// empty subrange [it, it):
|
||||
// empty buffer, not absolute
|
||||
{
|
||||
segments_view ps = parse_path("/a/b").value();
|
||||
auto it = ps.begin();
|
||||
segments_view sub(it, it);
|
||||
|
||||
BOOST_TEST_EQ(sub.size(), 0u);
|
||||
BOOST_TEST(!sub.is_absolute());
|
||||
BOOST_TEST_EQ(sub.buffer(), "");
|
||||
BOOST_TEST(sub.begin() == sub.end());
|
||||
}
|
||||
|
||||
// empty subrange from relative source
|
||||
{
|
||||
segments_view ps = parse_path("a/b").value();
|
||||
auto it = ps.begin();
|
||||
segments_view sub(it, it);
|
||||
|
||||
BOOST_TEST_EQ(sub.size(), 0u);
|
||||
BOOST_TEST(!sub.is_absolute());
|
||||
BOOST_TEST_EQ(sub.buffer(), "");
|
||||
BOOST_TEST(sub.begin() == sub.end());
|
||||
}
|
||||
|
||||
// percent-encoding:
|
||||
// slice of first encoded segment only, absolute start
|
||||
{
|
||||
segments_view ps = parse_path("/a%2Fb/c").value();
|
||||
auto first = ps.begin();
|
||||
auto last = std::next(ps.begin());
|
||||
segments_view sub(first, last);
|
||||
|
||||
// encoded in buffer
|
||||
BOOST_TEST(sub.is_absolute());
|
||||
BOOST_TEST_EQ(sub.size(), 1u);
|
||||
BOOST_TEST_EQ(sub.buffer(), "/a%2Fb");
|
||||
|
||||
// decoded on deref
|
||||
auto it = sub.begin();
|
||||
BOOST_TEST_EQ(*it++, "a/b");
|
||||
BOOST_TEST(it == sub.end());
|
||||
}
|
||||
|
||||
// aliasing: url_view -> segments_view -> subrange
|
||||
{
|
||||
url_view u("/x/y");
|
||||
segments_view ps = u.segments();
|
||||
segments_view sub(ps.begin(), ps.end());
|
||||
|
||||
BOOST_TEST_EQ(sub.buffer().data(), u.buffer().data());
|
||||
BOOST_TEST_EQ(sub.buffer(), "/x/y");
|
||||
BOOST_TEST(sub.is_absolute());
|
||||
BOOST_TEST_EQ(sub.size(), 2u);
|
||||
}
|
||||
|
||||
// empty prefix of absolute path:
|
||||
// [begin, begin) -> empty
|
||||
{
|
||||
segments_view ps = parse_path("/a/b/c").value();
|
||||
segments_view sub(ps.begin(), ps.begin());
|
||||
BOOST_TEST_EQ(sub.size(), 0u);
|
||||
BOOST_TEST_EQ(sub.buffer(), "");
|
||||
BOOST_TEST(!sub.is_absolute());
|
||||
}
|
||||
|
||||
// empty subrange in the middle (absolute):
|
||||
// ["b","b") -> "", not absolute
|
||||
{
|
||||
segments_view ps = parse_path("/a/b/c").value();
|
||||
auto b = std::next(ps.begin());
|
||||
segments_view sub(b, b);
|
||||
|
||||
BOOST_TEST_EQ(sub.size(), 0u);
|
||||
BOOST_TEST_EQ(sub.buffer(), "");
|
||||
BOOST_TEST(!sub.is_absolute());
|
||||
BOOST_TEST(sub.begin() == sub.end());
|
||||
}
|
||||
|
||||
// empty subrange in the middle (relative):
|
||||
// ["b","b") -> "", not absolute
|
||||
{
|
||||
segments_view ps = parse_path("a/b/c").value();
|
||||
auto b = std::next(ps.begin()); // "b"
|
||||
segments_view sub(b, b);
|
||||
|
||||
BOOST_TEST_EQ(sub.size(), 0u);
|
||||
BOOST_TEST_EQ(sub.buffer(), "");
|
||||
BOOST_TEST(!sub.is_absolute());
|
||||
BOOST_TEST(sub.begin() == sub.end());
|
||||
}
|
||||
|
||||
// empty subrange at end:
|
||||
// [end,end) -> "", not absolute
|
||||
{
|
||||
segments_view ps = parse_path("/a/b/c").value();
|
||||
auto e = ps.end();
|
||||
segments_view sub(e, e);
|
||||
|
||||
BOOST_TEST_EQ(sub.size(), 0u);
|
||||
BOOST_TEST_EQ(sub.buffer(), "");
|
||||
BOOST_TEST(!sub.is_absolute());
|
||||
}
|
||||
|
||||
// single-segment absolute path full slice: "/a"
|
||||
{
|
||||
segments_view ps = parse_path("/a").value();
|
||||
segments_view sub(ps.begin(), ps.end());
|
||||
|
||||
BOOST_TEST_EQ(sub.size(), 1u);
|
||||
BOOST_TEST(sub.is_absolute());
|
||||
BOOST_TEST_EQ(sub.buffer(), "/a");
|
||||
|
||||
auto it = sub.begin();
|
||||
BOOST_TEST_EQ(*it++, "a");
|
||||
BOOST_TEST(it == sub.end());
|
||||
}
|
||||
|
||||
// middle slice using last.pos path:
|
||||
// ["b", prev(end)) on "/a/b/c" -> "b"
|
||||
{
|
||||
segments_view ps = parse_path("/a/b/c").value();
|
||||
auto b = std::next(ps.begin());
|
||||
auto last = std::prev(ps.end());
|
||||
segments_view sub(b, last);
|
||||
|
||||
BOOST_TEST_EQ(sub.size(), 1u);
|
||||
BOOST_TEST(!sub.is_absolute());
|
||||
BOOST_TEST_EQ(sub.buffer(), "b");
|
||||
BOOST_TEST_EQ(*sub.begin(), "b");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testJavadocs()
|
||||
{
|
||||
@@ -135,6 +369,7 @@ struct segments_view_test
|
||||
run()
|
||||
{
|
||||
testSpecialMembers();
|
||||
testRangeCtor();
|
||||
testJavadocs();
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user