Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
Flamefire committed Mar 4, 2023
2 parents a93796e + cd3b01d commit 9ef67d7
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
- { name: GCC w/ sanitizers, sanitize: yes,
compiler: gcc-12, cxxstd: '03,11,14,17,20', os: ubuntu-22.04 }
- { name: Collect coverage, coverage: yes,
compiler: gcc-8, cxxstd: '03,11', os: ubuntu-20.04, install: 'g++-8-multilib', address-model: '32,64' }
compiler: gcc-8, cxxstd: '03,11,14,17,2a', os: ubuntu-20.04, install: 'g++-8-multilib', address-model: '32,64' }

# Linux, clang
- { compiler: clang-3.5, cxxstd: '03,11', os: ubuntu-20.04, container: 'ubuntu:16.04' }
Expand Down
10 changes: 7 additions & 3 deletions doc/changelog.dox
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2019-2021 Alexander Grund
// Copyright (c) 2019-2023 Alexander Grund
//
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
Expand All @@ -8,14 +8,18 @@

\section changelog Changelog

\subsection changelog_11_2_0 Nowide 11.2.0
\subsection changelog_11_2_1 Nowide 11.2.1 (Boost 1.82)
- Add `convert_string` overload accepting a string
- Add `quoted` to output (quoted) paths (std::filesystem or boost::filesystem)

\subsection changelog_11_2_0 Nowide 11.2.0 (Boost 1.80)
- `filebuf`: Major performance improvement for Bulk I/O
- `filebuf`: Fix wrong return value of `sync` when `fflush` failed
- `filebuf`: Fix possible undefined behavior in a corner case when nothing was actually written but buffer is in "write" mode
- `filebuf`: Limit putback of characters (i.e. `pbackfail`) only allowing putback of buffered characters (may be only 1 character)
- Add missing define `NOWIDE_USE_WCHAR_OVERLOADS` (standalone only)

\subsection changelog_11_1_4 Nowide 11.1.4
\subsection changelog_11_1_4 Nowide 11.1.4 (Boost 1.79)
- Fix possible redefinition of `_LARGEFILE_SOURCE`
- Fix missing include when `BOOST_USE_WINDOWS_H` and `WIN32_LEAN_AND_MEAN` are defined.
- Fix compile failures on recent MinGW-w64 compilers
Expand Down
24 changes: 24 additions & 0 deletions doc/main.dox
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,30 @@ However the `u8string()` member function can be used to obtain an UTF-8 encoded
And to optain a `path` from an UTF-8 encoded string you may use `std::filesystem::u8path`
or since C++20 one of the `path` constructors taking a `char8_t`-type input.

To read/write `std::filesystem::path` instances from/to streams you'd usually use e.g. `os << path`.
However that will effectively be run as `os << std::quoted(path.string())` which means a possible conversion
to a narrow string which may not be UTF-8 encoded.
For that \c quoted can be used:

\code
#include <boost/nowide/quoted.hpp>
#include <filesystem>
#include <sstream>

std::string write(const std::filesystem::path& path)
{
std::ostringstream s;
s << boost::nowide::quoted(path);
return s.str();
}

std::experimental::path read(std::istream& is)
{
std::filesystem::path path;
is >> boost::nowide::quoted(path);
return path;
}
\endcode

\section technical Technical Details
\subsection technical_imple Windows vs POSIX
Expand Down
109 changes: 109 additions & 0 deletions include/boost/nowide/quoted.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// Copyright (c) 2023 Alexander Grund
//
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#ifndef BOOST_NOWIDE_QUOTED_HPP_INCLUDED
#define BOOST_NOWIDE_QUOTED_HPP_INCLUDED

#include <boost/nowide/config.hpp>
#include <boost/nowide/detail/is_path.hpp>
#include <boost/nowide/utf/convert.hpp>
#include <iomanip>
#include <istream>
#include <ostream>
#include <type_traits>

#if defined(__cpp_lib_quoted_string_io) && __cpp_lib_quoted_string_io >= 201304

namespace boost {
namespace nowide {
/// \cond INTERNAL
namespace detail {
template<class Path>
struct quoted;
template<typename T>
using remove_cvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;

} // namespace detail
/// \endcond

/// \brief Allows insertion and extraction of `filesystem::path` into/from streams.
///
/// When used in an expression such as `out << quoted(path)`, where `out` is an output stream,
/// has the effect as-if `out << std::quoted(path.native())` was used.
///
/// When used in an expression like `in >> quoted(path)`, where `in` is an input stream,
/// has the effect as-if `in >> std::quoted(path.native())` was used if that would be valid.
/// To that effect a temporary string is used, which on success is assigned to `path`.
///
/// Will automatically convert between the streams `char_type` and `path::value_type` if neccessary.
template<class Path>
#ifdef BOOST_NOWIDE_DOXYGEN
unspecified_type
#else
detail::enable_if_path_t<detail::remove_cvref_t<Path>, detail::quoted<Path&>>
#endif
quoted(Path& path)
{
return {path};
}

/// \cond INTERNAL
// Same but for const-refs and r-values
template<class Path>
detail::enable_if_path_t<detail::remove_cvref_t<Path>, detail::quoted<const Path&>> quoted(const Path& path)
{
return {path};
}

namespace detail {
template<typename CharOut,
typename CharIn,
typename = typename std::enable_if<!std::is_same<CharOut, CharIn>::value>::type>
std::basic_string<CharOut> maybe_convert_string(const std::basic_string<CharIn>& s)
{
return utf::convert_string<CharOut>(s);
}
template<typename Char>
const std::basic_string<Char>& maybe_convert_string(const std::basic_string<Char>& s)
{
return s;
}

template<typename T>
using requires_non_const =
typename std::enable_if<!std::is_const<typename std::remove_reference<T>::type>::value>::type;

template<class Path>
struct quoted
{
Path value;
template<typename CharType>
friend std::basic_ostream<CharType>& operator<<(std::basic_ostream<CharType>& out, const quoted& path)
{
return out << std::quoted(maybe_convert_string<CharType>(path.value.native()));
}

template<typename CharType, class Path2 = Path, typename = requires_non_const<Path2>>
friend std::basic_istream<CharType>& operator>>(std::basic_istream<CharType>& in, const quoted& path)
{
std::basic_string<CharType> value;
using PlainPath = remove_cvref_t<Path>;
if(in >> std::quoted(value))
path.value = PlainPath(maybe_convert_string<typename PlainPath::value_type>(value));
return in;
}
};

} // namespace detail
/// \endcond
} // namespace nowide
} // namespace boost

#elif defined(BOOST_PRAGMA_MESSAGE)
BOOST_PRAGMA_MESSAGE("To use boost::nowide::quoted at least C++14 is required.")
#endif

#endif
11 changes: 11 additions & 0 deletions include/boost/nowide/utf/convert.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,17 @@ namespace nowide {
return result;
}

/// Convert the UTF sequence in the input string from \a CharIn to \a CharOut
/// and return it as a string
///
/// Any illegal sequences are replaced with the replacement character, see #BOOST_NOWIDE_REPLACEMENT_CHARACTER
/// \tparam CharOut Output character type
template<typename CharOut, typename CharIn>
std::basic_string<CharOut> convert_string(const std::basic_string<CharIn>& s)
{
return convert_string<CharOut>(s.data(), s.data() + s.size());
}

} // namespace utf
} // namespace nowide
} // namespace boost
Expand Down
99 changes: 98 additions & 1 deletion test/test_fs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,97 @@
#include <boost/nowide/convert.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/nowide/quoted.hpp>
#include <boost/nowide/utf/convert.hpp>
#include "test.hpp"
#include <iomanip>
#include <sstream>
#include <type_traits>
#if defined(_MSC_VER)
#pragma warning(disable : 4714) // function marked as __forceinline not inlined
#endif
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem.hpp>

// Exclude apple as support there is target level specific -.-
#if defined(__cpp_lib_filesystem) && !defined(__APPLE__)
#include <filesystem>
#define BOOST_NOWIDE_TEST_STD_PATH
#endif
#if defined(__cpp_lib_experimental_filesystem)
#ifndef _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
#endif
#include <experimental/filesystem>
#define BOOST_NOWIDE_TEST_STD_EXPERIMENTAL_PATH
#endif

template<typename T, typename = void>
struct is_istreamable : std::false_type
{};
using boost::nowide::detail::void_t;
template<typename T>
struct is_istreamable<T, void_t<decltype(std::declval<std::istream&>() >> std::declval<T>())>> : std::true_type
{};

template<typename T_Char>
std::string maybe_narrow(const std::basic_string<T_Char>& s)
{
return boost::nowide::narrow(s);
}

const std::string& maybe_narrow(const std::string& s)
{
return s;
}

template<class Path>
void test_fs_path_io(std::string utf8_name)
{
#if defined(__cpp_lib_quoted_string_io) && __cpp_lib_quoted_string_io >= 201304
Path path(boost::nowide::utf::convert_string<typename Path::value_type>(utf8_name));
// Get native and UTF-8/narrow name here as the Path ctor may change the string (e.g. slash substitution)
const auto nativeName = path.native();
utf8_name = maybe_narrow(nativeName);
// Output
std::ostringstream s, sRef;
sRef << std::quoted(utf8_name);
s << boost::nowide::quoted(path);
TEST_EQ(s.str(), sRef.str());
// const
const Path constPath(path);
s.str("");
s << boost::nowide::quoted(constPath);
TEST_EQ(s.str(), sRef.str());
// Rvalue
s.str("");
s << boost::nowide::quoted(Path(path));
TEST_EQ(s.str(), sRef.str());

// Input
std::istringstream sIn(sRef.str());
Path pathOut;
static_assert(is_istreamable<decltype(boost::nowide::quoted(pathOut))>::value, "!");
sIn >> boost::nowide::quoted(pathOut);
TEST_EQ(pathOut.native(), nativeName);
// Can't read into a const path
static_assert(!is_istreamable<decltype(boost::nowide::quoted(constPath))>::value, "!");
// or an Rvalue
static_assert(!is_istreamable<decltype(boost::nowide::quoted(Path(path)))>::value, "!");

// Wide stream
std::wostringstream ws, wsRef;
wsRef << std::quoted(boost::nowide::widen(utf8_name));
ws << boost::nowide::quoted(path);
TEST_EQ(ws.str(), wsRef.str());
std::wistringstream wsIn(wsRef.str());
pathOut.clear();
wsIn >> boost::nowide::quoted(pathOut);
TEST_EQ(maybe_narrow(pathOut.native()), utf8_name);
#else
(void)utf8_name; // Suppress unused warning
std::cout << "Skipping tests for boost::nowide::quoted" << std::endl;
#endif
}

// coverity[root_function]
void test_main(int, char** argv, char**)
Expand Down Expand Up @@ -63,4 +149,15 @@ void test_main(int, char** argv, char**)
TEST(test == "Test");
}
boost::filesystem::remove(path);

std::cout << "Testing boost::filesystem::path" << std::endl;
test_fs_path_io<boost::filesystem::path>(utf8_name);
#ifdef BOOST_NOWIDE_TEST_STD_EXPERIMENTAL_PATH
std::cout << "Testing std::experimental::filesystem::path" << std::endl;
test_fs_path_io<std::experimental::filesystem::path>(utf8_name);
#endif
#ifdef BOOST_NOWIDE_TEST_STD_PATH
std::cout << "Testing std::filesystem::path" << std::endl;
test_fs_path_io<std::filesystem::path>(utf8_name);
#endif
}

0 comments on commit 9ef67d7

Please sign in to comment.