Skip to content

Commit 9ef67d7

Browse files
committed
Merge branch 'develop'
2 parents a93796e + cd3b01d commit 9ef67d7

File tree

6 files changed

+250
-5
lines changed

6 files changed

+250
-5
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
- { name: GCC w/ sanitizers, sanitize: yes,
6161
compiler: gcc-12, cxxstd: '03,11,14,17,20', os: ubuntu-22.04 }
6262
- { name: Collect coverage, coverage: yes,
63-
compiler: gcc-8, cxxstd: '03,11', os: ubuntu-20.04, install: 'g++-8-multilib', address-model: '32,64' }
63+
compiler: gcc-8, cxxstd: '03,11,14,17,2a', os: ubuntu-20.04, install: 'g++-8-multilib', address-model: '32,64' }
6464

6565
# Linux, clang
6666
- { compiler: clang-3.5, cxxstd: '03,11', os: ubuntu-20.04, container: 'ubuntu:16.04' }

doc/changelog.dox

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (c) 2019-2021 Alexander Grund
2+
// Copyright (c) 2019-2023 Alexander Grund
33
//
44
// Distributed under the Boost Software License, Version 1.0.
55
// https://www.boost.org/LICENSE_1_0.txt
@@ -8,14 +8,18 @@
88

99
\section changelog Changelog
1010

11-
\subsection changelog_11_2_0 Nowide 11.2.0
11+
\subsection changelog_11_2_1 Nowide 11.2.1 (Boost 1.82)
12+
- Add `convert_string` overload accepting a string
13+
- Add `quoted` to output (quoted) paths (std::filesystem or boost::filesystem)
14+
15+
\subsection changelog_11_2_0 Nowide 11.2.0 (Boost 1.80)
1216
- `filebuf`: Major performance improvement for Bulk I/O
1317
- `filebuf`: Fix wrong return value of `sync` when `fflush` failed
1418
- `filebuf`: Fix possible undefined behavior in a corner case when nothing was actually written but buffer is in "write" mode
1519
- `filebuf`: Limit putback of characters (i.e. `pbackfail`) only allowing putback of buffered characters (may be only 1 character)
1620
- Add missing define `NOWIDE_USE_WCHAR_OVERLOADS` (standalone only)
1721

18-
\subsection changelog_11_1_4 Nowide 11.1.4
22+
\subsection changelog_11_1_4 Nowide 11.1.4 (Boost 1.79)
1923
- Fix possible redefinition of `_LARGEFILE_SOURCE`
2024
- Fix missing include when `BOOST_USE_WINDOWS_H` and `WIN32_LEAN_AND_MEAN` are defined.
2125
- Fix compile failures on recent MinGW-w64 compilers

doc/main.dox

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,30 @@ However the `u8string()` member function can be used to obtain an UTF-8 encoded
303303
And to optain a `path` from an UTF-8 encoded string you may use `std::filesystem::u8path`
304304
or since C++20 one of the `path` constructors taking a `char8_t`-type input.
305305

306+
To read/write `std::filesystem::path` instances from/to streams you'd usually use e.g. `os << path`.
307+
However that will effectively be run as `os << std::quoted(path.string())` which means a possible conversion
308+
to a narrow string which may not be UTF-8 encoded.
309+
For that \c quoted can be used:
310+
311+
\code
312+
#include <boost/nowide/quoted.hpp>
313+
#include <filesystem>
314+
#include <sstream>
315+
316+
std::string write(const std::filesystem::path& path)
317+
{
318+
std::ostringstream s;
319+
s << boost::nowide::quoted(path);
320+
return s.str();
321+
}
322+
323+
std::experimental::path read(std::istream& is)
324+
{
325+
std::filesystem::path path;
326+
is >> boost::nowide::quoted(path);
327+
return path;
328+
}
329+
\endcode
306330

307331
\section technical Technical Details
308332
\subsection technical_imple Windows vs POSIX

include/boost/nowide/quoted.hpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//
2+
// Copyright (c) 2023 Alexander Grund
3+
//
4+
// Distributed under the Boost Software License, Version 1.0.
5+
// https://www.boost.org/LICENSE_1_0.txt
6+
7+
#ifndef BOOST_NOWIDE_QUOTED_HPP_INCLUDED
8+
#define BOOST_NOWIDE_QUOTED_HPP_INCLUDED
9+
10+
#include <boost/nowide/config.hpp>
11+
#include <boost/nowide/detail/is_path.hpp>
12+
#include <boost/nowide/utf/convert.hpp>
13+
#include <iomanip>
14+
#include <istream>
15+
#include <ostream>
16+
#include <type_traits>
17+
18+
#if defined(__cpp_lib_quoted_string_io) && __cpp_lib_quoted_string_io >= 201304
19+
20+
namespace boost {
21+
namespace nowide {
22+
/// \cond INTERNAL
23+
namespace detail {
24+
template<class Path>
25+
struct quoted;
26+
template<typename T>
27+
using remove_cvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
28+
29+
} // namespace detail
30+
/// \endcond
31+
32+
/// \brief Allows insertion and extraction of `filesystem::path` into/from streams.
33+
///
34+
/// When used in an expression such as `out << quoted(path)`, where `out` is an output stream,
35+
/// has the effect as-if `out << std::quoted(path.native())` was used.
36+
///
37+
/// When used in an expression like `in >> quoted(path)`, where `in` is an input stream,
38+
/// has the effect as-if `in >> std::quoted(path.native())` was used if that would be valid.
39+
/// To that effect a temporary string is used, which on success is assigned to `path`.
40+
///
41+
/// Will automatically convert between the streams `char_type` and `path::value_type` if neccessary.
42+
template<class Path>
43+
#ifdef BOOST_NOWIDE_DOXYGEN
44+
unspecified_type
45+
#else
46+
detail::enable_if_path_t<detail::remove_cvref_t<Path>, detail::quoted<Path&>>
47+
#endif
48+
quoted(Path& path)
49+
{
50+
return {path};
51+
}
52+
53+
/// \cond INTERNAL
54+
// Same but for const-refs and r-values
55+
template<class Path>
56+
detail::enable_if_path_t<detail::remove_cvref_t<Path>, detail::quoted<const Path&>> quoted(const Path& path)
57+
{
58+
return {path};
59+
}
60+
61+
namespace detail {
62+
template<typename CharOut,
63+
typename CharIn,
64+
typename = typename std::enable_if<!std::is_same<CharOut, CharIn>::value>::type>
65+
std::basic_string<CharOut> maybe_convert_string(const std::basic_string<CharIn>& s)
66+
{
67+
return utf::convert_string<CharOut>(s);
68+
}
69+
template<typename Char>
70+
const std::basic_string<Char>& maybe_convert_string(const std::basic_string<Char>& s)
71+
{
72+
return s;
73+
}
74+
75+
template<typename T>
76+
using requires_non_const =
77+
typename std::enable_if<!std::is_const<typename std::remove_reference<T>::type>::value>::type;
78+
79+
template<class Path>
80+
struct quoted
81+
{
82+
Path value;
83+
template<typename CharType>
84+
friend std::basic_ostream<CharType>& operator<<(std::basic_ostream<CharType>& out, const quoted& path)
85+
{
86+
return out << std::quoted(maybe_convert_string<CharType>(path.value.native()));
87+
}
88+
89+
template<typename CharType, class Path2 = Path, typename = requires_non_const<Path2>>
90+
friend std::basic_istream<CharType>& operator>>(std::basic_istream<CharType>& in, const quoted& path)
91+
{
92+
std::basic_string<CharType> value;
93+
using PlainPath = remove_cvref_t<Path>;
94+
if(in >> std::quoted(value))
95+
path.value = PlainPath(maybe_convert_string<typename PlainPath::value_type>(value));
96+
return in;
97+
}
98+
};
99+
100+
} // namespace detail
101+
/// \endcond
102+
} // namespace nowide
103+
} // namespace boost
104+
105+
#elif defined(BOOST_PRAGMA_MESSAGE)
106+
BOOST_PRAGMA_MESSAGE("To use boost::nowide::quoted at least C++14 is required.")
107+
#endif
108+
109+
#endif

include/boost/nowide/utf/convert.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,17 @@ namespace nowide {
9090
return result;
9191
}
9292

93+
/// Convert the UTF sequence in the input string from \a CharIn to \a CharOut
94+
/// and return it as a string
95+
///
96+
/// Any illegal sequences are replaced with the replacement character, see #BOOST_NOWIDE_REPLACEMENT_CHARACTER
97+
/// \tparam CharOut Output character type
98+
template<typename CharOut, typename CharIn>
99+
std::basic_string<CharOut> convert_string(const std::basic_string<CharIn>& s)
100+
{
101+
return convert_string<CharOut>(s.data(), s.data() + s.size());
102+
}
103+
93104
} // namespace utf
94105
} // namespace nowide
95106
} // namespace boost

test/test_fs.cpp

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,97 @@
1313
#include <boost/nowide/convert.hpp>
1414
#include <boost/nowide/cstdio.hpp>
1515
#include <boost/nowide/fstream.hpp>
16+
#include <boost/nowide/quoted.hpp>
17+
#include <boost/nowide/utf/convert.hpp>
1618
#include "test.hpp"
19+
#include <iomanip>
20+
#include <sstream>
21+
#include <type_traits>
1722
#if defined(_MSC_VER)
1823
#pragma warning(disable : 4714) // function marked as __forceinline not inlined
1924
#endif
20-
#include <boost/filesystem/operations.hpp>
25+
#include <boost/filesystem.hpp>
26+
27+
// Exclude apple as support there is target level specific -.-
28+
#if defined(__cpp_lib_filesystem) && !defined(__APPLE__)
29+
#include <filesystem>
30+
#define BOOST_NOWIDE_TEST_STD_PATH
31+
#endif
32+
#if defined(__cpp_lib_experimental_filesystem)
33+
#ifndef _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
34+
#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
35+
#endif
36+
#include <experimental/filesystem>
37+
#define BOOST_NOWIDE_TEST_STD_EXPERIMENTAL_PATH
38+
#endif
39+
40+
template<typename T, typename = void>
41+
struct is_istreamable : std::false_type
42+
{};
43+
using boost::nowide::detail::void_t;
44+
template<typename T>
45+
struct is_istreamable<T, void_t<decltype(std::declval<std::istream&>() >> std::declval<T>())>> : std::true_type
46+
{};
47+
48+
template<typename T_Char>
49+
std::string maybe_narrow(const std::basic_string<T_Char>& s)
50+
{
51+
return boost::nowide::narrow(s);
52+
}
53+
54+
const std::string& maybe_narrow(const std::string& s)
55+
{
56+
return s;
57+
}
58+
59+
template<class Path>
60+
void test_fs_path_io(std::string utf8_name)
61+
{
62+
#if defined(__cpp_lib_quoted_string_io) && __cpp_lib_quoted_string_io >= 201304
63+
Path path(boost::nowide::utf::convert_string<typename Path::value_type>(utf8_name));
64+
// Get native and UTF-8/narrow name here as the Path ctor may change the string (e.g. slash substitution)
65+
const auto nativeName = path.native();
66+
utf8_name = maybe_narrow(nativeName);
67+
// Output
68+
std::ostringstream s, sRef;
69+
sRef << std::quoted(utf8_name);
70+
s << boost::nowide::quoted(path);
71+
TEST_EQ(s.str(), sRef.str());
72+
// const
73+
const Path constPath(path);
74+
s.str("");
75+
s << boost::nowide::quoted(constPath);
76+
TEST_EQ(s.str(), sRef.str());
77+
// Rvalue
78+
s.str("");
79+
s << boost::nowide::quoted(Path(path));
80+
TEST_EQ(s.str(), sRef.str());
81+
82+
// Input
83+
std::istringstream sIn(sRef.str());
84+
Path pathOut;
85+
static_assert(is_istreamable<decltype(boost::nowide::quoted(pathOut))>::value, "!");
86+
sIn >> boost::nowide::quoted(pathOut);
87+
TEST_EQ(pathOut.native(), nativeName);
88+
// Can't read into a const path
89+
static_assert(!is_istreamable<decltype(boost::nowide::quoted(constPath))>::value, "!");
90+
// or an Rvalue
91+
static_assert(!is_istreamable<decltype(boost::nowide::quoted(Path(path)))>::value, "!");
92+
93+
// Wide stream
94+
std::wostringstream ws, wsRef;
95+
wsRef << std::quoted(boost::nowide::widen(utf8_name));
96+
ws << boost::nowide::quoted(path);
97+
TEST_EQ(ws.str(), wsRef.str());
98+
std::wistringstream wsIn(wsRef.str());
99+
pathOut.clear();
100+
wsIn >> boost::nowide::quoted(pathOut);
101+
TEST_EQ(maybe_narrow(pathOut.native()), utf8_name);
102+
#else
103+
(void)utf8_name; // Suppress unused warning
104+
std::cout << "Skipping tests for boost::nowide::quoted" << std::endl;
105+
#endif
106+
}
21107

22108
// coverity[root_function]
23109
void test_main(int, char** argv, char**)
@@ -63,4 +149,15 @@ void test_main(int, char** argv, char**)
63149
TEST(test == "Test");
64150
}
65151
boost::filesystem::remove(path);
152+
153+
std::cout << "Testing boost::filesystem::path" << std::endl;
154+
test_fs_path_io<boost::filesystem::path>(utf8_name);
155+
#ifdef BOOST_NOWIDE_TEST_STD_EXPERIMENTAL_PATH
156+
std::cout << "Testing std::experimental::filesystem::path" << std::endl;
157+
test_fs_path_io<std::experimental::filesystem::path>(utf8_name);
158+
#endif
159+
#ifdef BOOST_NOWIDE_TEST_STD_PATH
160+
std::cout << "Testing std::filesystem::path" << std::endl;
161+
test_fs_path_io<std::filesystem::path>(utf8_name);
162+
#endif
66163
}

0 commit comments

Comments
 (0)