From 03d14c3beb4d06d70aa7bb2e4cf381de6b350afd Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 2 Jun 2024 07:01:40 -0700 Subject: [PATCH] Add support for multiple namespaces --- doc/api.md | 104 ++++++++++-------- include/fmt/base.h | 3 +- include/fmt/format.h | 28 +++-- support/mkdocstrings_handlers/cxx/__init__.py | 40 ++++--- 4 files changed, 95 insertions(+), 80 deletions(-) diff --git a/doc/api.md b/doc/api.md index 21b49b25..6a1cee7c 100644 --- a/doc/api.md +++ b/doc/api.md @@ -1,6 +1,6 @@ # API Reference -The {fmt} library API consists of the following parts: +The {fmt} library API consists of the following components: - [`fmt/base.h`](#base-api): the base API providing main formatting functions for `char`/UTF-8 with C++20 compile-time checks and minimal dependencies @@ -112,32 +112,36 @@ The recommended way of defining a formatter is by reusing an existing one via inheritance or composition. This way you can support standard format specifiers without implementing them yourself. For example: - // color.h: - #include +```c++ +// color.h: +#include - enum class color {red, green, blue}; +enum class color {red, green, blue}; - template <> struct fmt::formatter: formatter { - // parse is inherited from formatter. +template <> struct fmt::formatter: formatter { + // parse is inherited from formatter. - auto format(color c, format_context& ctx) const - -> format_context::iterator; - }; + auto format(color c, format_context& ctx) const + -> format_context::iterator; +}; +``` - // color.cc: - #include "color.h" - #include +```c++ +// color.cc: +#include "color.h" +#include - auto fmt::formatter::format(color c, format_context& ctx) const - -> format_context::iterator { - string_view name = "unknown"; - switch (c) { - case color::red: name = "red"; break; - case color::green: name = "green"; break; - case color::blue: name = "blue"; break; - } - return formatter::format(name, ctx); - } +auto fmt::formatter::format(color c, format_context& ctx) const + -> format_context::iterator { + string_view name = "unknown"; + switch (c) { + case color::red: name = "red"; break; + case color::green: name = "green"; break; + case color::blue: name = "blue"; break; + } + return formatter::format(name, ctx); +} +``` Note that `formatter::format` is defined in `fmt/format.h` so it has to be included in the source file. Since `parse` is inherited @@ -213,36 +217,40 @@ formatters. You can also write a formatter for a hierarchy of classes: - // demo.h: - #include - #include +```c++ +// demo.h: +#include +#include - struct A { - virtual ~A() {} - virtual std::string name() const { return "A"; } - }; +struct A { + virtual ~A() {} + virtual std::string name() const { return "A"; } +}; - struct B : A { - virtual std::string name() const { return "B"; } - }; +struct B : A { + virtual std::string name() const { return "B"; } +}; - template - struct fmt::formatter::value, char>> : - fmt::formatter { - auto format(const A& a, format_context& ctx) const { - return fmt::formatter::format(a.name(), ctx); - } - }; +template +struct fmt::formatter::value, char>> : + fmt::formatter { + auto format(const A& a, format_context& ctx) const { + return fmt::formatter::format(a.name(), ctx); + } +}; +``` - // demo.cc: - #include "demo.h" - #include +```c++ +// demo.cc: +#include "demo.h" +#include - int main() { - B b; - A& a = b; - fmt::print("{}", a); // prints "B" - } +int main() { + B b; + A& a = b; + fmt::print("{}", a); // prints "B" +} +``` Providing both a `formatter` specialization and a `format_as` overload is disallowed. @@ -335,7 +343,7 @@ formatting functions and locale support. ::: group_digits(T) - +::: detail::buffer ::: basic_memory_buffer diff --git a/include/fmt/base.h b/include/fmt/base.h index b29f81b2..eb8193cf 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -149,7 +149,6 @@ import std; # define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 #endif -// Detect C++20 concepts #ifdef FMT_USE_CONCEPTS // Use the provided definition. #elif defined(__cpp_concepts) @@ -834,7 +833,7 @@ class compile_parse_context : public basic_format_parse_context { }; /// A contiguous memory buffer with an optional growing ability. It is an -// internal class and shouldn't be used directly, only via `memory_buffer`. +/// internal class and shouldn't be used directly, only via `memory_buffer`. template class buffer { private: T* ptr_; diff --git a/include/fmt/format.h b/include/fmt/format.h index 8661bec2..d0ae0e81 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -3869,21 +3869,19 @@ FMT_API auto vsystem_error(int error_code, string_view format_str, format_args args) -> std::system_error; /** - \rst - Constructs :class:`std::system_error` with a message formatted with - ``fmt::format(fmt, args...)``. - *error_code* is a system error code as given by ``errno``. - - **Example**:: - - // This throws std::system_error with the description - // cannot open file 'madeup': No such file or directory - // or similar (system message may vary). - const char* filename = "madeup"; - std::FILE* file = std::fopen(filename, "r"); - if (!file) - throw fmt::system_error(errno, "cannot open file '{}'", filename); - \endrst + * Constructs `std::system_error` with a message formatted with + * `fmt::format(fmt, args...)`. + * *error_code* is a system error code as given by `errno`. + * + * **Example**:: + * + * // This throws std::system_error with the description + * // cannot open file 'madeup': No such file or directory + * // or similar (system message may vary). + * const char* filename = "madeup"; + * std::FILE* file = std::fopen(filename, "r"); + * if (!file) + * throw fmt::system_error(errno, "cannot open file '{}'", filename); */ template auto system_error(int error_code, format_string fmt, T&&... args) diff --git a/support/mkdocstrings_handlers/cxx/__init__.py b/support/mkdocstrings_handlers/cxx/__init__.py index b6a111c8..8880e4c3 100644 --- a/support/mkdocstrings_handlers/cxx/__init__.py +++ b/support/mkdocstrings_handlers/cxx/__init__.py @@ -68,7 +68,7 @@ def get_description(node: et.Element) -> list[et.Element]: return node.findall('briefdescription/para') + \ node.findall('detaileddescription/para') -def clean_type(type: str) -> str: +def normalize_type(type: str) -> str: type = type.replace('< ', '<').replace(' >', '>') return type.replace(' &', '&').replace(' *', '*') @@ -79,7 +79,7 @@ def convert_type(type: et.Element) -> str: if ref.tail: result += ref.tail result += type.tail.strip() - return clean_type(result) + return normalize_type(result) def convert_params(func: et.Element) -> Definition: params = [] @@ -94,7 +94,7 @@ def convert_return_type(d: Definition, node: et.Element) -> None: if d.type == 'auto' or d.type == 'constexpr auto': parts = node.find('argsstring').text.split(' -> ') if len(parts) > 1: - d.trailing_return_type = clean_type(parts[1]) + d.trailing_return_type = normalize_type(parts[1]) def render_decl(d: Definition) -> None: text = '
'
@@ -131,6 +131,7 @@ class CxxHandler(BaseHandler):
     cmd = ['doxygen', '-']
     doc_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
     include_dir = os.path.join(os.path.dirname(doc_dir), 'include', 'fmt')
+    self._ns2doxyxml = {}
     self._doxyxml_dir = 'doxyxml'
     p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
     out, _ = p.communicate(input=r'''
@@ -173,18 +174,26 @@ class CxxHandler(BaseHandler):
     if p.returncode != 0:
         raise CalledProcessError(p.returncode, cmd)
 
-    # Load XML.
-    with open(os.path.join(self._doxyxml_dir, 'namespacefmt.xml')) as f:
-      self._doxyxml = et.parse(f)
-
   def collect(self, identifier: str, config: Mapping[str, Any]) -> Definition:
-    name = identifier
-    paren = name.find('(')
+    qual_name = 'fmt::' + identifier
+
     param_str = None
+    paren = qual_name.find('(')
     if paren > 0:
-      name, param_str = name[:paren], name[paren + 1:-1]
-      
-    nodes = self._doxyxml.findall(
+      qual_name, param_str = qual_name[:paren], qual_name[paren + 1:-1]
+    
+    colons = qual_name.rfind('::')
+    namespace, name = qual_name[:colons], qual_name[colons + 2:]
+
+    # Load XML.
+    doxyxml = self._ns2doxyxml.get(namespace)
+    if doxyxml is None:
+      path = f'namespace{namespace.replace("::", "_1_1")}.xml'
+      with open(os.path.join(self._doxyxml_dir, path)) as f:
+        doxyxml = et.parse(f)
+        self._ns2doxyxml[namespace] = doxyxml
+
+    nodes = doxyxml.findall(
       f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
     candidates = []
     for node in nodes:
@@ -206,13 +215,14 @@ class CxxHandler(BaseHandler):
       return d
     
     # Process a compound definition such as a struct.
-    cls = self._doxyxml.findall(f"compounddef/innerclass[.='fmt::{name}']")
+    cls = doxyxml.findall(f"compounddef/innerclass[.='{qual_name}']")
     if not cls:
       raise Exception(f'Cannot find {identifier}. Candidates: {candidates}')
-    with open(os.path.join(self._doxyxml_dir, cls[0].get('refid') + '.xml')) as f:
+    path = os.path.join(self._doxyxml_dir, cls[0].get('refid') + '.xml')
+    with open(path) as f:
       xml = et.parse(f)
       node = xml.find('compounddef')
-      d = Definition(name, node.get('kind'))
+      d = Definition(identifier, node.get('kind'))
       d.template_params = convert_template_params(node)
       d.desc = get_description(node)
       d.members = []