From 14cd8be6a8772bf71909cf9a5d9ae3805f30518c Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 1 Sep 2024 13:33:25 +0200 Subject: [PATCH] use libfmt --- .github/workflows/build.yml | 6 +- NEWS | 1 + README.rst | 1 + meson.build | 3 + src/ConfigParser.cxx | 142 +++++++++++++++++------------------ src/EditPlaylistPage.cxx | 6 +- src/FileBrowserPage.cxx | 8 +- src/LibraryPage.cxx | 10 ++- src/LyricsPage.cxx | 13 ++-- src/SearchPage.cxx | 28 +++---- src/Styles.cxx | 12 +-- src/lib/fmt/RuntimeError.cxx | 19 +++++ src/lib/fmt/RuntimeError.hxx | 34 +++++++++ src/lib/fmt/ToBuffer.hxx | 46 ++++++++++++ src/lib/fmt/ToSpan.hxx | 27 +++++++ src/lib/fmt/meson.build | 24 ++++++ src/net/Resolver.cxx | 12 +-- src/net/meson.build | 3 + src/util/RuntimeError.hxx | 40 ---------- src/util/StringBuffer.hxx | 90 ++++++++++++++++++++++ subprojects/.gitignore | 3 + subprojects/fmt.wrap | 13 ++++ 22 files changed, 385 insertions(+), 156 deletions(-) create mode 100644 src/lib/fmt/RuntimeError.cxx create mode 100644 src/lib/fmt/RuntimeError.hxx create mode 100644 src/lib/fmt/ToBuffer.hxx create mode 100644 src/lib/fmt/ToSpan.hxx create mode 100644 src/lib/fmt/meson.build delete mode 100644 src/util/RuntimeError.hxx create mode 100644 src/util/StringBuffer.hxx create mode 100644 subprojects/fmt.wrap diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 19144aae..dffdb908 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,7 +31,7 @@ jobs: cc: gcc-14 cxx: g++-14 ldflags: -fuse-ld=mold - packages: g++-14 mold + packages: g++-14 mold libfmt-dev meson_options: - compiler: gcc11 os: ubuntu-22.04 @@ -39,13 +39,13 @@ jobs: cxx: g++-11 ldflags: packages: g++-11 - meson_options: + meson_options: --force-fallback-for=fmt - compiler: clang os: ubuntu-24.04 cc: clang cxx: clang++ ldflags: -fuse-ld=lld - packages: clang lld + packages: clang lld libfmt-dev meson_options: runs-on: ${{ matrix.os }} diff --git a/NEWS b/NEWS index 3b8edcdb..93697914 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,6 @@ ncmpc 0.50 - not yet released * build: require Meson 0.60 +* require libfmt 9 * lyrics/musixmatch: add new lyrics extension * lyrics/google: fix partial loading of lyrics diff --git a/README.rst b/README.rst index eee681e2..87a1e6d7 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,7 @@ How to compile and install ncmpc You need: - a C++20 compliant compiler (e.g. gcc or clang) +- `libfmt `__ - `libmpdclient `__ 2.16 - `ncurses `__ - `Meson 0.60 `__ and `Ninja `__ diff --git a/meson.build b/meson.build index d0c80ed9..228a2c34 100644 --- a/meson.build +++ b/meson.build @@ -13,6 +13,7 @@ project('ncmpc', 'cpp', ) cc = meson.get_compiler('cpp') +compiler = cc conf = configuration_data() conf.set_quoted('PACKAGE', meson.project_name()) @@ -379,6 +380,7 @@ if host_machine.system() == 'windows' subdir('src/win') endif +subdir('src/lib/fmt') subdir('src/io') subdir('src/system') subdir('src/net') @@ -451,6 +453,7 @@ ncmpc = executable('ncmpc', curses_dep, lirc_dep, libmpdclient_dep, + fmt_dep, ], install: true ) diff --git a/src/ConfigParser.cxx b/src/ConfigParser.cxx index 503a66c2..2db79ea7 100644 --- a/src/ConfigParser.cxx +++ b/src/ConfigParser.cxx @@ -15,9 +15,9 @@ #include "screen_list.hxx" #include "PageMeta.hxx" #include "Options.hxx" +#include "lib/fmt/RuntimeError.hxx" #include "util/CharUtil.hxx" #include "util/PrintException.hxx" -#include "util/RuntimeError.hxx" #include "util/ScopeExit.hxx" #include "util/StringAPI.hxx" #include "util/StringStrip.hxx" @@ -105,8 +105,8 @@ static char * after_unquoted_word(char *p) { if (!is_word_char(*p)) - throw FormatRuntimeError("%s: %s", - _("Word expected"), p); + throw FmtRuntimeError("{}: {:?}", + _("Word expected"), p); ++p; @@ -149,8 +149,8 @@ NextUnquotedValue(char *&pp) *end = 0; pp = StripLeft(end + 1); } else - throw FormatRuntimeError("%s: %s", - _("Whitespace expected"), end); + throw FmtRuntimeError("{}: {:?}", + _("Whitespace expected"), end); return value; } @@ -163,8 +163,8 @@ NextQuotedValue(char *&pp) { char *p = pp; if (*p != '"') - throw FormatRuntimeError("%s: %s", - _("Quoted value expected"), p); + throw FmtRuntimeError("{}: {:?}", + _("Quoted value expected"), p); ++p; @@ -172,8 +172,8 @@ NextQuotedValue(char *&pp) char *end = strchr(p, '"'); if (end == nullptr) - throw FormatRuntimeError("%s: %s", - _("Closing quote missing"), p); + throw FmtRuntimeError("{}: {:?}", + _("Closing quote missing"), p); *end = 0; pp = end + 1; @@ -190,8 +190,8 @@ NextNameValue(char *&p) p = after_unquoted_word(p); if (*p != '=') - throw FormatRuntimeError("%s: %s", - _("Syntax error"), p); + throw FmtRuntimeError("{}: {:?}", + _("Syntax error"), p); *p++ = 0; @@ -210,9 +210,9 @@ parse_key_value(const char *str, const char **end) { auto result = ParseKeyName(str); if (result.first == -1) - throw FormatRuntimeError("%s: %s", - _("Malformed hotkey definition"), - result.second); + throw FmtRuntimeError("{}: {:?}", + _("Malformed hotkey definition"), + result.second); *end = result.second; return result.first; @@ -227,11 +227,11 @@ parse_key_definition(char *str) /* get the command name */ char *eq = strchr(str, '='); if (eq == nullptr) - throw FormatRuntimeError("%s: %s", - /* the hotkey configuration - line is incomplete */ - _("Incomplete hotkey configuration"), - str); + throw FmtRuntimeError("{}: {:?}", + /* the hotkey configuration line + is incomplete */ + _("Incomplete hotkey configuration"), + str); char *command_name = str; str = StripLeft(eq + 1); @@ -240,11 +240,11 @@ parse_key_definition(char *str) StripRight(command_name); const auto cmd = get_key_command_from_name(command_name); if (cmd == Command::NONE) - throw FormatRuntimeError("%s: %s", - /* the hotkey configuration - contains an unknown - command */ - _("Unknown command"), command_name); + throw FmtRuntimeError("{}: {:?}", + /* the hotkey configuration + contains an unknown + command */ + _("Unknown command"), command_name); /* parse key values */ size_t i = 0; @@ -273,16 +273,15 @@ ParseCurrentTimeDisplay(const char *str) else if (StringIsEqual(str, "none")) return CurrentTimeDisplay::NONE; else - throw FormatRuntimeError("%s: %s", - /* translators: ncmpc - supports displaying the - "elapsed" or "remaining" - time of a song being - played; in this case, the - configuration file - contained an invalid - setting */ - _("Bad time display type"), str); + throw FmtRuntimeError("{}: {:?}", + /* translators: ncmpc supports + displaying the "elapsed" or + "remaining" time of a song + being played; in this case, + the configuration file + contained an invalid + setting */ + _("Bad time display type"), str); } #ifdef ENABLE_COLORS @@ -344,8 +343,8 @@ parse_color_definition(char *str) /* get the command name */ short color = ParseColorNameOrNumber(str); if (color < 0) - throw FormatRuntimeError("%s: %s", - _("Bad color name"), str); + throw FmtRuntimeError("{}: {:?}", + _("Bad color name"), str); /* parse r,g,b values */ @@ -353,21 +352,21 @@ parse_color_definition(char *str) for (unsigned i = 0; i < 3; ++i) { char *next = after_comma(value), *endptr; if (*value == 0) - throw FormatRuntimeError("%s: %s", - _("Incomplete color definition"), - str); + throw FmtRuntimeError("{}: {:?}", + _("Incomplete color definition"), + str); rgb[i] = strtol(value, &endptr, 0); if (endptr == value || *endptr != 0) - throw FormatRuntimeError("%s: %s", - _("Invalid number"), value); + throw FmtRuntimeError("{}: {:?}", + _("Invalid number"), value); value = next; } if (*value != 0) - throw FormatRuntimeError("%s: %s", - _("Malformed color definition"), str); + throw FmtRuntimeError("{}: {:?}", + _("Malformed color definition"), str); colors_define(color, rgb[0], rgb[1], rgb[2]); } @@ -425,14 +424,13 @@ check_screen_list(char *value) const auto *page_meta = screen_lookup_name(name); if (page_meta == nullptr) - throw FormatRuntimeError("%s: %s", - /* an unknown screen - name was specified - in the - configuration - file */ - _("Unknown screen name"), - name); + throw FmtRuntimeError("{}: {:?}", + /* an unknown screen + name was specified in + the configuration + file */ + _("Unknown screen name"), + name); /* use PageMeta::name because screen_lookup_name() may have translated a @@ -459,8 +457,8 @@ ParseTagList(char *value) while (char *name = NextItem(value)) { auto type = mpd_tag_name_iparse(name); if (type == MPD_TAG_UNKNOWN) - throw FormatRuntimeError("%s: %s", - _("Unknown MPD tag"), name); + throw FmtRuntimeError("{}: {:?}", + _("Unknown MPD tag"), name); result.emplace_back(type); } @@ -486,8 +484,8 @@ get_search_mode(char *value) if (0 <= mode && mode <= 4) return mode; else - throw FormatRuntimeError("%s: %s", - _("Invalid search mode"),value); + throw FmtRuntimeError("{}: {:?}", + _("Invalid search mode"),value); } else { @@ -504,9 +502,9 @@ get_search_mode(char *value) else if (StringIsEqualIgnoreCase(value, "artist+album")) return 4; else - throw FormatRuntimeError("%s: %s", - _("Unknown search mode"), - value); + throw FmtRuntimeError("{}: {:?}", + _("Unknown search mode"), + value); } } @@ -537,18 +535,18 @@ ParseTableColumn(char *s) column.min_width = strtoul(value, &endptr, 10); if (endptr == value || *endptr != 0 || column.min_width == 0 || column.min_width > 1000) - throw FormatRuntimeError("%s: %s", - _("Invalid column width"), - value); + throw FmtRuntimeError("{}: {:?}", + _("Invalid column width"), + value); } else if (StringIsEqual(name, "fraction")) { char *endptr; column.fraction_width = strtod(value, &endptr); if (endptr == value || *endptr != 0 || column.fraction_width < 0 || column.fraction_width > 1000) - throw FormatRuntimeError("%s: %s", - _("Invalid column fraction width"), - value); + throw FmtRuntimeError("{}: {:?}", + _("Invalid column fraction width"), + value); } } @@ -581,8 +579,8 @@ parse_line(char *line) ++line; line = StripLeft(line); } else if (line == name_end) { - throw FormatRuntimeError("%s: %s", - _("Missing '='"), name_end); + throw FmtRuntimeError("{}: {:?}", + _("Missing '='"), name_end); } *name_end = 0; @@ -753,9 +751,9 @@ parse_line(char *line) options.second_column = str2bool(value); #endif else - throw FormatRuntimeError("%s: %s", - _("Unknown configuration parameter"), - name); + throw FmtRuntimeError("{}: {:?}", + _("Unknown configuration parameter"), + name); } bool @@ -784,9 +782,9 @@ ReadConfigFile(const char *filename) try { parse_line(p); } catch (...) { - fprintf(stderr, - "Failed to parse '%s' line %u: ", - filename, no); + fmt::print(stderr, + "Failed to parse {:?} line {}: ", + filename, no); PrintException(std::current_exception()); } } diff --git a/src/EditPlaylistPage.cxx b/src/EditPlaylistPage.cxx index ce3bc1f1..3bae7b79 100644 --- a/src/EditPlaylistPage.cxx +++ b/src/EditPlaylistPage.cxx @@ -12,12 +12,14 @@ #include "Options.hxx" #include "mpdclient.hxx" #include "screen.hxx" -#include "util/SPrintf.hxx" +#include "lib/fmt/ToSpan.hxx" #include #include +using std::string_view_literals::operator""sv; + static std::string next_playlist_name; class EditPlaylistPage final : public FileListPage { @@ -119,7 +121,7 @@ EditPlaylistPage::GetTitle(std::span buffer) const noexcept if (name.empty()) return _("Playlist"); - return SPrintf(buffer, "%s: %s", _("Playlist"), name.c_str()); + return FmtTruncate(buffer, "{}: {}"sv, _("Playlist"), name); } void diff --git a/src/FileBrowserPage.cxx b/src/FileBrowserPage.cxx index 39f358a4..5d7d7cad 100644 --- a/src/FileBrowserPage.cxx +++ b/src/FileBrowserPage.cxx @@ -16,7 +16,7 @@ #include "screen_client.hxx" #include "Command.hxx" #include "Options.hxx" -#include "util/SPrintf.hxx" +#include "lib/fmt/ToSpan.hxx" #include "util/UriUtil.hxx" #include @@ -286,9 +286,9 @@ FileBrowserPage::GetTitle(std::span buffer) const noexcept /* fall back to full path */ path = current_path.c_str(); - return SPrintf(buffer, "%s: %s", - /* translators: caption of the browser screen */ - _("Browse"), Utf8ToLocale(path).c_str()); + return FmtTruncate(buffer, "{}: {}", + /* translators: caption of the browser screen */ + _("Browse"), Utf8ToLocale(path).c_str()); } void diff --git a/src/LibraryPage.cxx b/src/LibraryPage.cxx index 062e7b11..4b6624bb 100644 --- a/src/LibraryPage.cxx +++ b/src/LibraryPage.cxx @@ -12,7 +12,7 @@ #include "mpdclient.hxx" #include "filelist.hxx" #include "Options.hxx" -#include "util/SPrintf.hxx" +#include "lib/fmt/ToSpan.hxx" #include #include @@ -21,6 +21,8 @@ #include #include +using std::string_view_literals::operator""sv; + [[gnu::const]] static const char * GetTagPlural(enum mpd_tag_type tag) noexcept @@ -38,14 +40,14 @@ GetTagPlural(enum mpd_tag_type tag) noexcept } static std::string_view -MakePageTitle(std::span buffer, const char *prefix, +MakePageTitle(std::span buffer, std::string_view prefix, const TagFilter &filter) { if (filter.empty()) return prefix; - return SPrintf(buffer, "%s: %s", prefix, - Utf8ToLocale(ToString(filter).c_str()).c_str()); + return FmtTruncate(buffer, "{}: {}"sv, prefix, + Utf8ToLocale{ToString(filter)}.c_str()); } class SongListPage final : public FileListPage { diff --git a/src/LyricsPage.cxx b/src/LyricsPage.cxx index 7618f01b..6d7a24c1 100644 --- a/src/LyricsPage.cxx +++ b/src/LyricsPage.cxx @@ -17,7 +17,7 @@ #include "TextPage.hxx" #include "screen_utils.hxx" #include "ncu.hxx" -#include "util/SPrintf.hxx" +#include "lib/fmt/ToSpan.hxx" #include "util/StringAPI.hxx" #include @@ -313,11 +313,12 @@ std::string_view LyricsPage::GetTitle(std::span buffer) const noexcept { if (plugin_cycle != nullptr) { - return SPrintf(buffer, "%s (%s)", - _("Lyrics"), - /* translators: this message is displayed - while data is retrieved */ - _("loading...")); + return FmtTruncate(buffer, "{} ({})", + _("Lyrics"), + /* translators: this message is + displayed while data is + retrieved */ + _("loading...")); } else if (artist != nullptr && title != nullptr && !IsEmpty()) { std::size_t n; n = snprintf(buffer.data(), buffer.size(), "%s: %s - %s", diff --git a/src/SearchPage.cxx b/src/SearchPage.cxx index 8a0dad25..021f8c83 100644 --- a/src/SearchPage.cxx +++ b/src/SearchPage.cxx @@ -14,13 +14,15 @@ #include "screen_utils.hxx" #include "FileListPage.hxx" #include "filelist.hxx" -#include "util/SPrintf.hxx" +#include "lib/fmt/ToSpan.hxx" #include "util/StringAPI.hxx" #include #include +using std::string_view_literals::operator""sv; + enum { SEARCH_URI = MPD_TAG_COUNT + 100, SEARCH_MODIFIED, @@ -131,15 +133,15 @@ class SearchHelpText final : public ListText { assert(idx < std::size(help_text)); if (idx == 0) - return SPrintf(buffer, " %s : %s", - GetGlobalKeyBindings().GetKeyNames(Command::SCREEN_SEARCH).c_str(), - "New search"); + return FmtTruncate(buffer, " {} : {}"sv, + GetGlobalKeyBindings().GetKeyNames(Command::SCREEN_SEARCH), + "New search"sv); if (idx == 1) - return SPrintf(buffer, " %s : %s [%s]", - GetGlobalKeyBindings().GetKeyNames(Command::SEARCH_MODE).c_str(), - get_key_description(Command::SEARCH_MODE), - my_gettext(mode[options.search_mode].label)); + return FmtTruncate(buffer, " {} : {} [{}]"sv, + GetGlobalKeyBindings().GetKeyNames(Command::SEARCH_MODE), + get_key_description(Command::SEARCH_MODE), + my_gettext(mode[options.search_mode].label)); return help_text[idx]; } @@ -442,12 +444,12 @@ std::string_view SearchPage::GetTitle(std::span buffer) const noexcept { if (advanced_search_mode && !pattern.empty()) - return SPrintf(buffer, "%s '%s'", _("Search"), pattern.c_str()); + return FmtTruncate(buffer, "{} '{}'"sv, _("Search"), pattern); else if (!pattern.empty()) - return SPrintf(buffer, "%s '%s' [%s]", - _("Search"), - pattern.c_str(), - my_gettext(mode[options.search_mode].label)); + return FmtTruncate(buffer, "{} '{}' [{}]", + _("Search"), + pattern, + my_gettext(mode[options.search_mode].label)); else return _("Search"); } diff --git a/src/Styles.cxx b/src/Styles.cxx index c2f706e4..119fba2a 100644 --- a/src/Styles.cxx +++ b/src/Styles.cxx @@ -5,7 +5,7 @@ #include "BasicColors.hxx" #include "CustomColors.hxx" #include "i18n.h" -#include "util/RuntimeError.hxx" +#include "lib/fmt/RuntimeError.hxx" #include "util/StringAPI.hxx" #include "util/StringStrip.hxx" #include "Window.hxx" @@ -232,7 +232,7 @@ ParseBackgroundColor(const char *s) if (StringIsEqualIgnoreCase(s, "none")) return COLOR_NONE; - throw FormatRuntimeError("%s: %s", _("Unknown color"), s); + throw FmtRuntimeError("{}: {:?}", _("Unknown color"), s); } /** @@ -292,8 +292,8 @@ ParseStyle(StyleData &d, const char *str) else if (StringIsEqualIgnoreCase(cur, "bold")) d.attr |= A_BOLD; else - throw FormatRuntimeError("%s: %s", - _("Unknown color"), str); + throw FmtRuntimeError("{}: {:?}", + _("Unknown color"), str); } } @@ -302,8 +302,8 @@ ModifyStyle(const char *name, const char *value) { const auto style = StyleByName(name); if (style == Style::END) - throw FormatRuntimeError("%s: %s", - _("Unknown color field"), name); + throw FmtRuntimeError("{}: {:?}", + _("Unknown color field"), name); auto &data = GetStyle(style); diff --git a/src/lib/fmt/RuntimeError.cxx b/src/lib/fmt/RuntimeError.cxx new file mode 100644 index 00000000..27c55a21 --- /dev/null +++ b/src/lib/fmt/RuntimeError.cxx @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: BSD-2-Clause +// author: Max Kellermann + +#include "RuntimeError.hxx" +#include "ToBuffer.hxx" + +std::runtime_error +VFmtRuntimeError(fmt::string_view format_str, fmt::format_args args) noexcept +{ + const auto msg = VFmtBuffer<512>(format_str, args); + return std::runtime_error{msg}; +} + +std::invalid_argument +VFmtInvalidArgument(fmt::string_view format_str, fmt::format_args args) noexcept +{ + const auto msg = VFmtBuffer<512>(format_str, args); + return std::invalid_argument{msg}; +} diff --git a/src/lib/fmt/RuntimeError.hxx b/src/lib/fmt/RuntimeError.hxx new file mode 100644 index 00000000..61623ec3 --- /dev/null +++ b/src/lib/fmt/RuntimeError.hxx @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BSD-2-Clause +// author: Max Kellermann + +#pragma once + +#include + +#include // IWYU pragma: export + +[[nodiscard]] [[gnu::pure]] +std::runtime_error +VFmtRuntimeError(fmt::string_view format_str, fmt::format_args args) noexcept; + +template +[[nodiscard]] [[gnu::pure]] +auto +FmtRuntimeError(const S &format_str, Args&&... args) noexcept +{ + return VFmtRuntimeError(format_str, + fmt::make_format_args(args...)); +} + +[[nodiscard]] [[gnu::pure]] +std::invalid_argument +VFmtInvalidArgument(fmt::string_view format_str, fmt::format_args args) noexcept; + +template +[[nodiscard]] [[gnu::pure]] +auto +FmtInvalidArgument(const S &format_str, Args&&... args) noexcept +{ + return VFmtInvalidArgument(format_str, + fmt::make_format_args(args...)); +} diff --git a/src/lib/fmt/ToBuffer.hxx b/src/lib/fmt/ToBuffer.hxx new file mode 100644 index 00000000..01f6102f --- /dev/null +++ b/src/lib/fmt/ToBuffer.hxx @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BSD-2-Clause +// author: Max Kellermann + +#pragma once + +#include "util/StringBuffer.hxx" + +#include + +template +StringBuffer & +VFmtToBuffer(StringBuffer &buffer, + fmt::string_view format_str, fmt::format_args args) noexcept +{ + auto [p, _] = fmt::vformat_to_n(buffer.begin(), buffer.capacity() - 1, + format_str, args); + *p = 0; + return buffer; +} + +template +[[nodiscard]] [[gnu::pure]] +auto +VFmtBuffer(fmt::string_view format_str, fmt::format_args args) noexcept +{ + StringBuffer buffer; + return VFmtToBuffer(buffer, format_str, args); +} + +template +StringBuffer & +FmtToBuffer(StringBuffer &buffer, + const S &format_str, Args&&... args) noexcept +{ + return VFmtToBuffer(buffer, format_str, + fmt::make_format_args(args...)); +} + +template +[[nodiscard]] [[gnu::pure]] +auto +FmtBuffer(const S &format_str, Args&&... args) noexcept +{ + return VFmtBuffer(format_str, + fmt::make_format_args(args...)); +} diff --git a/src/lib/fmt/ToSpan.hxx b/src/lib/fmt/ToSpan.hxx new file mode 100644 index 00000000..bc9239c7 --- /dev/null +++ b/src/lib/fmt/ToSpan.hxx @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: BSD-2-Clause +// author: Max Kellermann + +#pragma once + +#include + +#include +#include + +[[nodiscard]] [[gnu::pure]] +inline std::string_view +VFmtTruncate(std::span buffer, + fmt::string_view format_str, fmt::format_args args) noexcept +{ + auto [p, _] = fmt::vformat_to_n(buffer.begin(), buffer.size(), + format_str, args); + return {buffer.begin(), p}; +} + +template +[[nodiscard]] [[gnu::pure]] +inline std::string_view +FmtTruncate(std::span buffer, const S &format_str, Args&&... args) noexcept +{ + return VFmtTruncate(buffer, format_str, fmt::make_format_args(args...)); +} diff --git a/src/lib/fmt/meson.build b/src/lib/fmt/meson.build new file mode 100644 index 00000000..4e4d9269 --- /dev/null +++ b/src/lib/fmt/meson.build @@ -0,0 +1,24 @@ +# using include_type:system to work around -Wfloat-equal +libfmt = dependency('fmt', version: '>= 9', + include_type: 'system', + fallback: ['fmt', 'fmt_dep']) + +if compiler.get_id() == 'gcc' and compiler.version().version_compare('>=13') and compiler.version().version_compare('<15') + libfmt = declare_dependency( + dependencies: libfmt, + # suppress bogus GCC 13 warnings: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109717 + compile_args: ['-Wno-array-bounds', '-Wno-stringop-overflow'] + ) +endif + +fmt = static_library( + 'fmt', + 'RuntimeError.cxx', + include_directories: inc, + dependencies: libfmt, +) + +fmt_dep = declare_dependency( + link_with: fmt, + dependencies: libfmt, +) diff --git a/src/net/Resolver.cxx b/src/net/Resolver.cxx index 016fc906..ec94baa1 100644 --- a/src/net/Resolver.cxx +++ b/src/net/Resolver.cxx @@ -5,7 +5,7 @@ #include "Resolver.hxx" #include "AddressInfo.hxx" #include "HostParser.hxx" -#include "util/RuntimeError.hxx" +#include "lib/fmt/RuntimeError.hxx" #include "util/CharUtil.hxx" #include "util/StringAPI.hxx" @@ -26,10 +26,10 @@ Resolve(const char *node, const char *service, struct addrinfo *ai; int error = getaddrinfo(node, service, hints, &ai); if (error != 0) - throw FormatRuntimeError("Failed to resolve '%s':'%s': %s", - node == nullptr ? "" : node, - service == nullptr ? "" : service, - gai_strerror(error)); + throw FmtRuntimeError("Failed to resolve {:?}:{:?}: {}", + node == nullptr ? "" : node, + service == nullptr ? "" : service, + gai_strerror(error)); return AddressInfoList(ai); } @@ -60,7 +60,7 @@ FindAndResolveInterfaceName(char *host, size_t size) const unsigned i = if_nametoindex(interface); if (i == 0) - throw FormatRuntimeError("No such interface: %s", interface); + throw FmtRuntimeError("No such interface: {}", interface); sprintf(interface, "%u", i); } diff --git a/src/net/meson.build b/src/net/meson.build index 4c234053..d552fbfb 100644 --- a/src/net/meson.build +++ b/src/net/meson.build @@ -17,6 +17,9 @@ net = static_library( 'SocketAddress.cxx', 'SocketDescriptor.cxx', include_directories: inc, + dependencies: [ + fmt_dep, + ], ) net_dep = declare_dependency( diff --git a/src/util/RuntimeError.hxx b/src/util/RuntimeError.hxx deleted file mode 100644 index 94b7c6ee..00000000 --- a/src/util/RuntimeError.hxx +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: BSD-2-Clause -// author: Max Kellermann - -#ifndef RUNTIME_ERROR_HXX -#define RUNTIME_ERROR_HXX - -#include // IWYU pragma: export -#include - -#include - -#if defined(__clang__) || defined(__GNUC__) -#pragma GCC diagnostic push -// TODO: fix this warning properly -#pragma GCC diagnostic ignored "-Wformat-security" -#endif - -template -static inline std::runtime_error -FormatRuntimeError(const char *fmt, Args&&... args) noexcept -{ - char buffer[1024]; - snprintf(buffer, sizeof(buffer), fmt, std::forward(args)...); - return std::runtime_error(buffer); -} - -template -inline std::invalid_argument -FormatInvalidArgument(const char *fmt, Args&&... args) noexcept -{ - char buffer[1024]; - snprintf(buffer, sizeof(buffer), fmt, std::forward(args)...); - return std::invalid_argument(buffer); -} - -#if defined(__clang__) || defined(__GNUC__) -#pragma GCC diagnostic pop -#endif - -#endif diff --git a/src/util/StringBuffer.hxx b/src/util/StringBuffer.hxx new file mode 100644 index 00000000..e25d42cb --- /dev/null +++ b/src/util/StringBuffer.hxx @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: BSD-2-Clause +// author: Max Kellermann + +#pragma once + +#include + +/** + * A statically allocated string buffer. + */ +template +class BasicStringBuffer { +public: + using value_type = T; + using reference = T &; + using pointer = T *; + using const_pointer = const T *; + using size_type = std::size_t; + + static constexpr value_type SENTINEL = '\0'; + +protected: + using Array = std::array; + Array the_data; + +public: + using iterator = typename Array::iterator; + using const_iterator = typename Array::const_iterator; + + static constexpr size_type capacity() noexcept { + return CAPACITY; + } + + constexpr bool empty() const noexcept { + return front() == SENTINEL; + } + + constexpr void clear() noexcept { + the_data[0] = SENTINEL; + } + + constexpr const_pointer c_str() const noexcept { + return the_data.data(); + } + + constexpr pointer data() noexcept { + return the_data.data(); + } + + constexpr value_type front() const noexcept { + return the_data.front(); + } + + /** + * Returns one character. No bounds checking. + */ + constexpr value_type operator[](size_type i) const noexcept { + return the_data[i]; + } + + /** + * Returns one writable character. No bounds checking. + */ + constexpr reference operator[](size_type i) noexcept { + return the_data[i]; + } + + constexpr iterator begin() noexcept { + return the_data.begin(); + } + + constexpr iterator end() noexcept { + return the_data.end(); + } + + constexpr const_iterator begin() const noexcept { + return the_data.begin(); + } + + constexpr const_iterator end() const noexcept { + return the_data.end(); + } + + constexpr operator const_pointer() const noexcept { + return c_str(); + } +}; + +template +class StringBuffer : public BasicStringBuffer {}; diff --git a/subprojects/.gitignore b/subprojects/.gitignore index f6c4ee43..877887fc 100644 --- a/subprojects/.gitignore +++ b/subprojects/.gitignore @@ -1 +1,4 @@ +/packagecache/ + +/fmt-*/ /libmpdclient/ diff --git a/subprojects/fmt.wrap b/subprojects/fmt.wrap new file mode 100644 index 00000000..42b61596 --- /dev/null +++ b/subprojects/fmt.wrap @@ -0,0 +1,13 @@ +[wrap-file] +directory = fmt-11.0.1 +source_url = https://github.com/fmtlib/fmt/archive/11.0.1.tar.gz +source_filename = fmt-11.0.1.tar.gz +source_hash = 7d009f7f89ac84c0a83f79ed602463d092fbf66763766a907c97fd02b100f5e9 +patch_filename = fmt_11.0.1-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/fmt_11.0.1-1/get_patch +patch_hash = 0a8b93d1ee6d84a82d3872a9bfb4c3977d8a53f7f484d42d1f7ed63ed496d549 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_11.0.1-1/fmt-11.0.1.tar.gz +wrapdb_version = 11.0.1-1 + +[provide] +fmt = fmt_dep