mirror of
https://github.com/fmtlib/fmt.git
synced 2025-07-30 18:57:34 +02:00
Improve locale support
This commit is contained in:
@ -138,8 +138,8 @@ function(add_headers VAR)
|
|||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
# Define the fmt library, its includes and the needed defines.
|
# Define the fmt library, its includes and the needed defines.
|
||||||
add_headers(FMT_HEADERS color.h core.h format.h format-inl.h ostream.h printf.h
|
add_headers(FMT_HEADERS color.h core.h format.h format-inl.h locale.h ostream.h
|
||||||
time.h ranges.h)
|
printf.h time.h ranges.h)
|
||||||
set(FMT_SOURCES src/format.cc)
|
set(FMT_SOURCES src/format.cc)
|
||||||
if (HAVE_OPEN)
|
if (HAVE_OPEN)
|
||||||
add_headers(FMT_HEADERS posix.h)
|
add_headers(FMT_HEADERS posix.h)
|
||||||
|
@ -960,6 +960,22 @@ class arg_map {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A type-erased reference to an std::locale to avoid heavy <locale> include.
|
||||||
|
class locale_ref {
|
||||||
|
private:
|
||||||
|
const void *locale_; // A type-erased pointer to std::locale.
|
||||||
|
friend class locale;
|
||||||
|
|
||||||
|
public:
|
||||||
|
locale_ref() : locale_(FMT_NULL) {}
|
||||||
|
|
||||||
|
template <typename Locale>
|
||||||
|
explicit locale_ref(const Locale &loc);
|
||||||
|
|
||||||
|
template <typename Locale>
|
||||||
|
Locale get() const;
|
||||||
|
};
|
||||||
|
|
||||||
template <typename OutputIt, typename Context, typename Char>
|
template <typename OutputIt, typename Context, typename Char>
|
||||||
class context_base {
|
class context_base {
|
||||||
public:
|
public:
|
||||||
@ -969,14 +985,16 @@ class context_base {
|
|||||||
basic_parse_context<Char> parse_context_;
|
basic_parse_context<Char> parse_context_;
|
||||||
iterator out_;
|
iterator out_;
|
||||||
basic_format_args<Context> args_;
|
basic_format_args<Context> args_;
|
||||||
|
locale_ref loc_;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
typedef Char char_type;
|
typedef Char char_type;
|
||||||
typedef basic_format_arg<Context> format_arg;
|
typedef basic_format_arg<Context> format_arg;
|
||||||
|
|
||||||
context_base(OutputIt out, basic_string_view<char_type> format_str,
|
context_base(OutputIt out, basic_string_view<char_type> format_str,
|
||||||
basic_format_args<Context> ctx_args)
|
basic_format_args<Context> ctx_args,
|
||||||
: parse_context_(format_str), out_(out), args_(ctx_args) {}
|
locale_ref loc = locale_ref())
|
||||||
|
: parse_context_(format_str), out_(out), args_(ctx_args), loc_(loc) {}
|
||||||
|
|
||||||
// Returns the argument with specified index.
|
// Returns the argument with specified index.
|
||||||
format_arg do_get_arg(unsigned arg_id) {
|
format_arg do_get_arg(unsigned arg_id) {
|
||||||
@ -1009,6 +1027,8 @@ class context_base {
|
|||||||
|
|
||||||
// Advances the begin iterator to ``it``.
|
// Advances the begin iterator to ``it``.
|
||||||
void advance_to(iterator it) { out_ = it; }
|
void advance_to(iterator it) { out_ = it; }
|
||||||
|
|
||||||
|
locale_ref locale() { return loc_; }
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Context, typename T>
|
template <typename Context, typename T>
|
||||||
@ -1078,8 +1098,9 @@ class basic_format_context :
|
|||||||
stored in the object so make sure they have appropriate lifetimes.
|
stored in the object so make sure they have appropriate lifetimes.
|
||||||
*/
|
*/
|
||||||
basic_format_context(OutputIt out, basic_string_view<char_type> format_str,
|
basic_format_context(OutputIt out, basic_string_view<char_type> format_str,
|
||||||
basic_format_args<basic_format_context> ctx_args)
|
basic_format_args<basic_format_context> ctx_args,
|
||||||
: base(out, format_str, ctx_args) {}
|
internal::locale_ref loc = internal::locale_ref())
|
||||||
|
: base(out, format_str, ctx_args, loc) {}
|
||||||
|
|
||||||
format_arg next_arg() {
|
format_arg next_arg() {
|
||||||
return this->do_get_arg(this->parse_context().next_arg_id());
|
return this->do_get_arg(this->parse_context().next_arg_id());
|
||||||
|
@ -203,25 +203,28 @@ FMT_FUNC size_t internal::count_code_points(basic_string_view<char8_t> s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||||
class locale {
|
|
||||||
private:
|
|
||||||
std::locale locale_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit locale(std::locale loc = std::locale()) : locale_(loc) {}
|
|
||||||
std::locale get() { return locale_; }
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
|
template <typename Locale>
|
||||||
|
locale_ref::locale_ref(const Locale &loc) : locale_(&loc) {
|
||||||
|
static_assert(std::is_same<Locale, std::locale>::value, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Locale>
|
||||||
|
Locale locale_ref::get() const {
|
||||||
|
static_assert(std::is_same<Locale, std::locale>::value, "");
|
||||||
|
return locale_ ? *static_cast<const std::locale*>(locale_) : std::locale();
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
FMT_FUNC Char thousands_sep_impl(locale_provider *lp) {
|
FMT_FUNC Char thousands_sep_impl(locale_ref loc) {
|
||||||
std::locale loc = lp ? lp->locale().get() : std::locale();
|
return std::use_facet<std::numpunct<Char> >(
|
||||||
return std::use_facet<std::numpunct<Char>>(loc).thousands_sep();
|
loc.get<std::locale>()).thousands_sep();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
FMT_FUNC Char internal::thousands_sep(locale_provider *lp) {
|
FMT_FUNC Char internal::thousands_sep(locale_ref) {
|
||||||
return FMT_STATIC_THOUSANDS_SEPARATOR;
|
return FMT_STATIC_THOUSANDS_SEPARATOR;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -959,10 +962,6 @@ FMT_FUNC void vprint(wstring_view format_str, wformat_args args) {
|
|||||||
vprint(stdout, format_str, args);
|
vprint(stdout, format_str, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
|
||||||
FMT_FUNC locale locale_provider::locale() { return fmt::locale(); }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
|
@ -422,16 +422,6 @@ inline u8string_view operator"" _u(const char *s, std::size_t n) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// A wrapper around std::locale used to reduce compile times since <locale>
|
|
||||||
// is very heavy.
|
|
||||||
class locale;
|
|
||||||
|
|
||||||
class locale_provider {
|
|
||||||
public:
|
|
||||||
virtual ~locale_provider() {}
|
|
||||||
virtual fmt::locale locale();
|
|
||||||
};
|
|
||||||
|
|
||||||
// The number of characters to store in the basic_memory_buffer object itself
|
// The number of characters to store in the basic_memory_buffer object itself
|
||||||
// to avoid dynamic memory allocation.
|
// to avoid dynamic memory allocation.
|
||||||
enum { inline_buffer_size = 500 };
|
enum { inline_buffer_size = 500 };
|
||||||
@ -1034,16 +1024,16 @@ class add_thousands_sep {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
FMT_API Char thousands_sep_impl(locale_provider *lp);
|
FMT_API Char thousands_sep_impl(locale_ref loc);
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
inline Char thousands_sep(locale_provider *lp) {
|
inline Char thousands_sep(locale_ref loc) {
|
||||||
return Char(thousands_sep_impl<char>(lp));
|
return Char(thousands_sep_impl<char>(loc));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline wchar_t thousands_sep(locale_provider *lp) {
|
inline wchar_t thousands_sep(locale_ref loc) {
|
||||||
return thousands_sep_impl<wchar_t>(lp);
|
return thousands_sep_impl<wchar_t>(loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Formats a decimal unsigned integer value writing into buffer.
|
// Formats a decimal unsigned integer value writing into buffer.
|
||||||
@ -1449,7 +1439,8 @@ class arg_formatter_base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
arg_formatter_base(Range r, format_specs *s): writer_(r), specs_(s) {}
|
arg_formatter_base(Range r, format_specs *s, locale_ref loc)
|
||||||
|
: writer_(r, loc), specs_(s) {}
|
||||||
|
|
||||||
iterator operator()(monostate) {
|
iterator operator()(monostate) {
|
||||||
FMT_ASSERT(false, "invalid argument type");
|
FMT_ASSERT(false, "invalid argument type");
|
||||||
@ -2320,7 +2311,7 @@ class arg_formatter:
|
|||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
explicit arg_formatter(context_type &ctx, format_specs *spec = FMT_NULL)
|
explicit arg_formatter(context_type &ctx, format_specs *spec = FMT_NULL)
|
||||||
: base(Range(ctx.out()), spec), ctx_(ctx) {}
|
: base(Range(ctx.out()), spec, ctx.locale()), ctx_(ctx) {}
|
||||||
|
|
||||||
// Deprecated.
|
// Deprecated.
|
||||||
arg_formatter(context_type &ctx, format_specs &spec)
|
arg_formatter(context_type &ctx, format_specs &spec)
|
||||||
@ -2408,7 +2399,7 @@ class basic_writer {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
iterator out_; // Output iterator.
|
iterator out_; // Output iterator.
|
||||||
std::unique_ptr<locale_provider> locale_;
|
internal::locale_ref locale_;
|
||||||
|
|
||||||
iterator out() const { return out_; }
|
iterator out() const { return out_; }
|
||||||
|
|
||||||
@ -2608,7 +2599,7 @@ class basic_writer {
|
|||||||
|
|
||||||
void on_num() {
|
void on_num() {
|
||||||
unsigned num_digits = internal::count_digits(abs_value);
|
unsigned num_digits = internal::count_digits(abs_value);
|
||||||
char_type sep = internal::thousands_sep<char_type>(writer.locale_.get());
|
char_type sep = internal::thousands_sep<char_type>(writer.locale_);
|
||||||
unsigned size = num_digits + SEP_SIZE * ((num_digits - 1) / 3);
|
unsigned size = num_digits + SEP_SIZE * ((num_digits - 1) / 3);
|
||||||
writer.write_int(size, get_prefix(), spec,
|
writer.write_int(size, get_prefix(), spec,
|
||||||
num_writer{abs_value, size, sep});
|
num_writer{abs_value, size, sep});
|
||||||
@ -2698,7 +2689,9 @@ class basic_writer {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
/** Constructs a ``basic_writer`` object. */
|
/** Constructs a ``basic_writer`` object. */
|
||||||
explicit basic_writer(Range out): out_(out.begin()) {}
|
explicit basic_writer(
|
||||||
|
Range out, internal::locale_ref loc = internal::locale_ref())
|
||||||
|
: out_(out.begin()), locale_(loc) {}
|
||||||
|
|
||||||
void write(int value) { write_decimal(value); }
|
void write(int value) { write_decimal(value); }
|
||||||
void write(long value) { write_decimal(value); }
|
void write(long value) { write_decimal(value); }
|
||||||
@ -3226,8 +3219,9 @@ struct format_handler : internal::error_handler {
|
|||||||
typedef typename ArgFormatter::range range;
|
typedef typename ArgFormatter::range range;
|
||||||
|
|
||||||
format_handler(range r, basic_string_view<Char> str,
|
format_handler(range r, basic_string_view<Char> str,
|
||||||
basic_format_args<Context> format_args)
|
basic_format_args<Context> format_args,
|
||||||
: context(r.begin(), str, format_args) {}
|
internal::locale_ref loc)
|
||||||
|
: context(r.begin(), str, format_args, loc) {}
|
||||||
|
|
||||||
void on_text(const Char *begin, const Char *end) {
|
void on_text(const Char *begin, const Char *end) {
|
||||||
auto size = internal::to_unsigned(end - begin);
|
auto size = internal::to_unsigned(end - begin);
|
||||||
@ -3277,10 +3271,12 @@ struct format_handler : internal::error_handler {
|
|||||||
|
|
||||||
/** Formats arguments and writes the output to the range. */
|
/** Formats arguments and writes the output to the range. */
|
||||||
template <typename ArgFormatter, typename Char, typename Context>
|
template <typename ArgFormatter, typename Char, typename Context>
|
||||||
typename Context::iterator vformat_to(typename ArgFormatter::range out,
|
typename Context::iterator vformat_to(
|
||||||
basic_string_view<Char> format_str,
|
typename ArgFormatter::range out,
|
||||||
basic_format_args<Context> args) {
|
basic_string_view<Char> format_str,
|
||||||
format_handler<ArgFormatter, Char, Context> h(out, format_str, args);
|
basic_format_args<Context> args,
|
||||||
|
internal::locale_ref loc = internal::locale_ref()) {
|
||||||
|
format_handler<ArgFormatter, Char, Context> h(out, format_str, args, loc);
|
||||||
internal::parse_format_string<false>(format_str, h);
|
internal::parse_format_string<false>(format_str, h);
|
||||||
return h.context.out();
|
return h.context.out();
|
||||||
}
|
}
|
||||||
|
47
include/fmt/locale.h
Normal file
47
include/fmt/locale.h
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Formatting library for C++ - std::locale support
|
||||||
|
//
|
||||||
|
// Copyright (c) 2012 - present, Victor Zverovich
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
#ifndef FMT_LOCALE_H_
|
||||||
|
#define FMT_LOCALE_H_
|
||||||
|
|
||||||
|
#include "format.h"
|
||||||
|
#include <locale>
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
template <typename Char>
|
||||||
|
typename buffer_context<Char>::type::iterator vformat_to(
|
||||||
|
const std::locale &loc, basic_buffer<Char> &buf,
|
||||||
|
basic_string_view<Char> format_str,
|
||||||
|
basic_format_args<typename buffer_context<Char>::type> args) {
|
||||||
|
typedef back_insert_range<basic_buffer<Char> > range;
|
||||||
|
return vformat_to<arg_formatter<range>>(
|
||||||
|
buf, to_string_view(format_str), args, internal::locale_ref(loc));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
std::basic_string<Char> vformat(
|
||||||
|
const std::locale &loc, basic_string_view<Char> format_str,
|
||||||
|
basic_format_args<typename buffer_context<Char>::type> args) {
|
||||||
|
basic_memory_buffer<Char> buffer;
|
||||||
|
internal::vformat_to(loc, buffer, format_str, args);
|
||||||
|
return fmt::to_string(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S, typename... Args>
|
||||||
|
inline std::basic_string<FMT_CHAR(S)> format(
|
||||||
|
const std::locale &loc, const S &format_str, const Args &... args) {
|
||||||
|
return internal::vformat(
|
||||||
|
loc, to_string_view(format_str),
|
||||||
|
*internal::checked_args<S, Args...>(format_str, args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
#endif // FMT_LOCALE_H_
|
@ -1,6 +1,6 @@
|
|||||||
// Formatting library for C++ - std::ostream support
|
// Formatting library for C++ - std::ostream support
|
||||||
//
|
//
|
||||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
// Copyright (c) 2012 - present, Victor Zverovich
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
// For the license information refer to format.h.
|
// For the license information refer to format.h.
|
||||||
|
@ -243,7 +243,8 @@ class printf_arg_formatter:
|
|||||||
*/
|
*/
|
||||||
printf_arg_formatter(internal::basic_buffer<char_type> &buffer,
|
printf_arg_formatter(internal::basic_buffer<char_type> &buffer,
|
||||||
format_specs &spec, context_type &ctx)
|
format_specs &spec, context_type &ctx)
|
||||||
: base(back_insert_range<internal::basic_buffer<char_type>>(buffer), &spec),
|
: base(back_insert_range<internal::basic_buffer<char_type>>(buffer), &spec,
|
||||||
|
ctx.locale()),
|
||||||
context_(ctx) {}
|
context_(ctx) {}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -9,10 +9,11 @@
|
|||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
template struct internal::basic_data<void>;
|
template struct internal::basic_data<void>;
|
||||||
|
template internal::locale_ref::locale_ref(const std::locale &loc);
|
||||||
|
|
||||||
// Explicit instantiations for char.
|
// Explicit instantiations for char.
|
||||||
|
|
||||||
template FMT_API char internal::thousands_sep_impl(locale_provider *lp);
|
template FMT_API char internal::thousands_sep_impl(locale_ref);
|
||||||
|
|
||||||
template void internal::basic_buffer<char>::append(const char *, const char *);
|
template void internal::basic_buffer<char>::append(const char *, const char *);
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ template FMT_API void internal::sprintf_format(
|
|||||||
|
|
||||||
// Explicit instantiations for wchar_t.
|
// Explicit instantiations for wchar_t.
|
||||||
|
|
||||||
template FMT_API wchar_t internal::thousands_sep_impl(locale_provider *);
|
template FMT_API wchar_t internal::thousands_sep_impl(locale_ref);
|
||||||
|
|
||||||
template void internal::basic_buffer<wchar_t>::append(
|
template void internal::basic_buffer<wchar_t>::append(
|
||||||
const wchar_t *, const wchar_t *);
|
const wchar_t *, const wchar_t *);
|
||||||
|
@ -89,6 +89,7 @@ add_fmt_test(core-test)
|
|||||||
add_fmt_test(gtest-extra-test)
|
add_fmt_test(gtest-extra-test)
|
||||||
add_fmt_test(format-test mock-allocator.h)
|
add_fmt_test(format-test mock-allocator.h)
|
||||||
add_fmt_test(format-impl-test)
|
add_fmt_test(format-impl-test)
|
||||||
|
add_fmt_test(locale-test)
|
||||||
add_fmt_test(ostream-test)
|
add_fmt_test(ostream-test)
|
||||||
add_fmt_test(printf-test)
|
add_fmt_test(printf-test)
|
||||||
add_fmt_test(time-test)
|
add_fmt_test(time-test)
|
||||||
|
@ -603,17 +603,6 @@ TEST(StringViewTest, Ctor) {
|
|||||||
EXPECT_EQ(4u, string_view(std::string("defg")).size());
|
EXPECT_EQ(4u, string_view(std::string("defg")).size());
|
||||||
}
|
}
|
||||||
|
|
||||||
// GCC 4.6 doesn't have std::is_copy_*.
|
|
||||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION >= 407
|
|
||||||
TEST(WriterTest, NotCopyConstructible) {
|
|
||||||
EXPECT_FALSE(std::is_copy_constructible<fmt::writer>::value);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(WriterTest, NotCopyAssignable) {
|
|
||||||
EXPECT_FALSE(std::is_copy_assignable<fmt::writer>::value);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST(WriterTest, Data) {
|
TEST(WriterTest, Data) {
|
||||||
memory_buffer buf;
|
memory_buffer buf;
|
||||||
fmt::writer w(buf);
|
fmt::writer w(buf);
|
||||||
@ -1947,7 +1936,7 @@ class mock_arg_formatter:
|
|||||||
typedef buffer_range range;
|
typedef buffer_range range;
|
||||||
|
|
||||||
mock_arg_formatter(fmt::format_context &ctx, fmt::format_specs *s = FMT_NULL)
|
mock_arg_formatter(fmt::format_context &ctx, fmt::format_specs *s = FMT_NULL)
|
||||||
: base(fmt::internal::get_container(ctx.out()), s) {
|
: base(fmt::internal::get_container(ctx.out()), s, ctx.locale()) {
|
||||||
EXPECT_CALL(*this, call(42));
|
EXPECT_CALL(*this, call(42));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
21
test/locale-test.cc
Normal file
21
test/locale-test.cc
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Formatting library for C++ - core tests
|
||||||
|
//
|
||||||
|
// Copyright (c) 2012 - present, Victor Zverovich
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
#include "fmt/locale.h"
|
||||||
|
#include "gmock.h"
|
||||||
|
|
||||||
|
struct numpunct : std::numpunct<char> {
|
||||||
|
protected:
|
||||||
|
char do_thousands_sep() const FMT_OVERRIDE { return '~'; }
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(LocaleTest, Format) {
|
||||||
|
std::locale loc;
|
||||||
|
EXPECT_EQ("1~234~567",
|
||||||
|
fmt::format(std::locale(loc, new numpunct()), "{:n}", 1234567));
|
||||||
|
EXPECT_EQ("1,234,567", fmt::format(loc, "{:n}", 1234567));
|
||||||
|
}
|
Reference in New Issue
Block a user