Improve path formatter

This commit is contained in:
Victor Zverovich 2023-07-20 17:18:03 -07:00
parent 40f35d6f04
commit ac0ab8eff3
3 changed files with 47 additions and 18 deletions

View File

@ -569,8 +569,8 @@ template <typename Container, FMT_ENABLE_IF(is_contiguous<Container>::value)>
__attribute__((no_sanitize("undefined")))
#endif
inline auto
reserve(std::back_insert_iterator<Container> it, size_t n)
-> typename Container::value_type* {
reserve(std::back_insert_iterator<Container> it, size_t n) ->
typename Container::value_type* {
Container& c = get_container(it);
size_t size = c.size();
c.resize(size + n);
@ -1251,7 +1251,7 @@ FMT_CONSTEXPR auto count_digits(UInt n) -> int {
FMT_INLINE auto do_count_digits(uint32_t n) -> int {
// An optimization by Kendall Willets from https://bit.ly/3uOIQrB.
// This increments the upper 32 bits (log10(T) - 1) when >= T is added.
# define FMT_INC(T) (((sizeof(# T) - 1ull) << 32) - T)
# define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T)
static constexpr uint64_t table[] = {
FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8
FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64
@ -1412,6 +1412,8 @@ class utf8_to_utf16 {
auto str() const -> std::wstring { return {&buffer_[0], size()}; }
};
enum class to_utf8_error_policy { abort, replace };
// A converter from UTF-16/UTF-32 (host endian) to UTF-8.
template <typename WChar, typename Buffer = memory_buffer> class to_utf8 {
private:
@ -1440,17 +1442,21 @@ template <typename WChar, typename Buffer = memory_buffer> class to_utf8 {
buffer_.push_back(0);
return true;
}
static bool convert(Buffer& buf, basic_string_view<WChar> s) {
static bool convert(
Buffer& buf, basic_string_view<WChar> s,
to_utf8_error_policy policy = to_utf8_error_policy::abort) {
for (auto p = s.begin(); p != s.end(); ++p) {
uint32_t c = static_cast<uint32_t>(*p);
if (sizeof(WChar) == 2 && c >= 0xd800 && c <= 0xdfff) {
// surrogate pair
// Handle a surrogate pair.
++p;
if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) {
return false;
}
if (policy == to_utf8_error_policy::abort) return false;
buf.append(string_view("<EFBFBD>"));
} else {
c = (c << 10) + static_cast<uint32_t>(*p) - 0x35fdc00;
}
}
if (c < 0x80) {
buf.push_back(static_cast<char>(c));
} else if (c < 0x800) {
@ -4147,7 +4153,9 @@ template <> struct formatter<bytes> {
};
// group_digits_view is not derived from view because it copies the argument.
template <typename T> struct group_digits_view { T value; };
template <typename T> struct group_digits_view {
T value;
};
/**
\rst

View File

@ -61,22 +61,32 @@ FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char> auto get_path_string(const std::filesystem::path& p) {
return p.string<Char>();
}
template <typename Char>
void write_escaped_path(basic_memory_buffer<Char>& quoted,
const std::filesystem::path& p) {
write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
}
# ifdef _WIN32
template <>
auto get_path_string<char>(const std::filesystem::path& p) {
return to_utf8<wchar_t>(p.native());
}
template <>
inline void write_escaped_path<char>(memory_buffer& quoted,
const std::filesystem::path& p) {
auto buf = basic_memory_buffer<wchar_t>();
write_escaped_string<wchar_t>(std::back_inserter(buf), p.native());
// Convert UTF-16 to UTF-8.
if (!to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()}))
FMT_THROW(std::runtime_error("invalid utf16"));
bool valid = to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()});
FMT_ASSERT(valid, "invalid utf16");
}
# endif
# endif // _WIN32
template <>
inline void write_escaped_path<std::filesystem::path::value_type>(
basic_memory_buffer<std::filesystem::path::value_type>& quoted,
@ -92,8 +102,11 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
private:
format_specs<Char> specs_;
detail::arg_ref<Char> width_ref_;
bool debug_ = false;
public:
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
auto it = ctx.begin(), end = ctx.end();
if (it == end) return it;
@ -102,7 +115,10 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
if (it == end) return it;
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
if (it != end && *it == '?') ++it;
if (it != end && *it == '?') {
debug_ = true;
++it;
}
return it;
}
@ -111,6 +127,10 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
auto specs = specs_;
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
ctx);
if (!debug_) {
auto s = detail::get_path_string<Char>(p);
return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
}
auto quoted = basic_memory_buffer<Char>();
detail::write_escaped_path(quoted, p);
return detail::write(ctx.out(),

View File

@ -19,9 +19,9 @@ using testing::StartsWith;
#ifdef __cpp_lib_filesystem
TEST(std_test, path) {
EXPECT_EQ(fmt::format("{:8}", std::filesystem::path("foo")), "\"foo\" ");
EXPECT_EQ(fmt::format("{:8}", std::filesystem::path("foo")), "foo ");
EXPECT_EQ(fmt::format("{}", std::filesystem::path("foo\"bar.txt")),
"\"foo\\\"bar.txt\"");
"foo\"bar.txt");
EXPECT_EQ(fmt::format("{:?}", std::filesystem::path("foo\"bar.txt")),
"\"foo\\\"bar.txt\"");
@ -29,8 +29,9 @@ TEST(std_test, path) {
EXPECT_EQ(fmt::format("{}", std::filesystem::path(
L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
L"\x0447\x044B\x043D\x0430")),
"\"Шчучыншчына\"");
EXPECT_EQ(fmt::format("{}", std::filesystem::path(L"\xd800")), "\"\\ud800\"");
"Шчучыншчына");
EXPECT_EQ(fmt::format("{:?}", std::filesystem::path(L"\xd800")),
"\"\\ud800\"");
# endif
}
@ -39,7 +40,7 @@ TEST(ranges_std_test, format_vector_path) {
auto p = std::filesystem::path("foo/bar.txt");
auto c = std::vector<std::string>{"abc", "def"};
EXPECT_EQ(fmt::format("path={}, range={}", p, c),
"path=\"foo/bar.txt\", range=[\"abc\", \"def\"]");
"path=foo/bar.txt, range=[\"abc\", \"def\"]");
}
// Test that path is not escaped twice in the debug mode.