Add support for multiple namespaces

This commit is contained in:
Victor Zverovich
2024-06-02 07:01:40 -07:00
parent a10e032148
commit 03d14c3beb
4 changed files with 95 additions and 80 deletions

View File

@ -1,6 +1,6 @@
# API Reference # 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 - [`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 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 one via inheritance or composition. This way you can support standard
format specifiers without implementing them yourself. For example: format specifiers without implementing them yourself. For example:
// color.h: ```c++
#include <fmt/base.h> // color.h:
#include <fmt/base.h>
enum class color {red, green, blue}; enum class color {red, green, blue};
template <> struct fmt::formatter<color>: formatter<string_view> { template <> struct fmt::formatter<color>: formatter<string_view> {
// parse is inherited from formatter<string_view>. // parse is inherited from formatter<string_view>.
auto format(color c, format_context& ctx) const auto format(color c, format_context& ctx) const
-> format_context::iterator; -> format_context::iterator;
}; };
```
// color.cc: ```c++
#include "color.h" // color.cc:
#include <fmt/format.h> #include "color.h"
#include <fmt/format.h>
auto fmt::formatter<color>::format(color c, format_context& ctx) const auto fmt::formatter<color>::format(color c, format_context& ctx) const
-> format_context::iterator { -> format_context::iterator {
string_view name = "unknown"; string_view name = "unknown";
switch (c) { switch (c) {
case color::red: name = "red"; break; case color::red: name = "red"; break;
case color::green: name = "green"; break; case color::green: name = "green"; break;
case color::blue: name = "blue"; break; case color::blue: name = "blue"; break;
} }
return formatter<string_view>::format(name, ctx); return formatter<string_view>::format(name, ctx);
} }
```
Note that `formatter<string_view>::format` is defined in `fmt/format.h` Note that `formatter<string_view>::format` is defined in `fmt/format.h`
so it has to be included in the source file. Since `parse` is inherited 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: You can also write a formatter for a hierarchy of classes:
// demo.h: ```c++
#include <type_traits> // demo.h:
#include <fmt/core.h> #include <type_traits>
#include <fmt/core.h>
struct A { struct A {
virtual ~A() {} virtual ~A() {}
virtual std::string name() const { return "A"; } virtual std::string name() const { return "A"; }
}; };
struct B : A { struct B : A {
virtual std::string name() const { return "B"; } virtual std::string name() const { return "B"; }
}; };
template <typename T> template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> : struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> :
fmt::formatter<std::string> { fmt::formatter<std::string> {
auto format(const A& a, format_context& ctx) const { auto format(const A& a, format_context& ctx) const {
return fmt::formatter<std::string>::format(a.name(), ctx); return fmt::formatter<std::string>::format(a.name(), ctx);
} }
}; };
```
// demo.cc: ```c++
#include "demo.h" // demo.cc:
#include <fmt/format.h> #include "demo.h"
#include <fmt/format.h>
int main() { int main() {
B b; B b;
A& a = b; A& a = b;
fmt::print("{}", a); // prints "B" fmt::print("{}", a); // prints "B"
} }
```
Providing both a `formatter` specialization and a `format_as` overload Providing both a `formatter` specialization and a `format_as` overload
is disallowed. is disallowed.
@ -335,7 +343,7 @@ formatting functions and locale support.
::: group_digits(T) ::: group_digits(T)
<!-- TODO ::: detail::buffer --> ::: detail::buffer
::: basic_memory_buffer ::: basic_memory_buffer

View File

@ -149,7 +149,6 @@ import std;
# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 # define FMT_USE_NONTYPE_TEMPLATE_ARGS 0
#endif #endif
// Detect C++20 concepts
#ifdef FMT_USE_CONCEPTS #ifdef FMT_USE_CONCEPTS
// Use the provided definition. // Use the provided definition.
#elif defined(__cpp_concepts) #elif defined(__cpp_concepts)
@ -834,7 +833,7 @@ class compile_parse_context : public basic_format_parse_context<Char> {
}; };
/// A contiguous memory buffer with an optional growing ability. It is an /// 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 <typename T> class buffer { template <typename T> class buffer {
private: private:
T* ptr_; T* ptr_;

View File

@ -3869,21 +3869,19 @@ FMT_API auto vsystem_error(int error_code, string_view format_str,
format_args args) -> std::system_error; format_args args) -> std::system_error;
/** /**
\rst * Constructs `std::system_error` with a message formatted with
Constructs :class:`std::system_error` with a message formatted with * `fmt::format(fmt, args...)`.
``fmt::format(fmt, args...)``. * *error_code* is a system error code as given by `errno`.
*error_code* is a system error code as given by ``errno``. *
* **Example**::
**Example**:: *
* // This throws std::system_error with the description
// This throws std::system_error with the description * // cannot open file 'madeup': No such file or directory
// cannot open file 'madeup': No such file or directory * // or similar (system message may vary).
// or similar (system message may vary). * const char* filename = "madeup";
const char* filename = "madeup"; * std::FILE* file = std::fopen(filename, "r");
std::FILE* file = std::fopen(filename, "r"); * if (!file)
if (!file) * throw fmt::system_error(errno, "cannot open file '{}'", filename);
throw fmt::system_error(errno, "cannot open file '{}'", filename);
\endrst
*/ */
template <typename... T> template <typename... T>
auto system_error(int error_code, format_string<T...> fmt, T&&... args) auto system_error(int error_code, format_string<T...> fmt, T&&... args)

View File

@ -68,7 +68,7 @@ def get_description(node: et.Element) -> list[et.Element]:
return node.findall('briefdescription/para') + \ return node.findall('briefdescription/para') + \
node.findall('detaileddescription/para') node.findall('detaileddescription/para')
def clean_type(type: str) -> str: def normalize_type(type: str) -> str:
type = type.replace('< ', '<').replace(' >', '>') type = type.replace('< ', '<').replace(' >', '>')
return type.replace(' &', '&').replace(' *', '*') return type.replace(' &', '&').replace(' *', '*')
@ -79,7 +79,7 @@ def convert_type(type: et.Element) -> str:
if ref.tail: if ref.tail:
result += ref.tail result += ref.tail
result += type.tail.strip() result += type.tail.strip()
return clean_type(result) return normalize_type(result)
def convert_params(func: et.Element) -> Definition: def convert_params(func: et.Element) -> Definition:
params = [] params = []
@ -94,7 +94,7 @@ def convert_return_type(d: Definition, node: et.Element) -> None:
if d.type == 'auto' or d.type == 'constexpr auto': if d.type == 'auto' or d.type == 'constexpr auto':
parts = node.find('argsstring').text.split(' -> ') parts = node.find('argsstring').text.split(' -> ')
if len(parts) > 1: 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: def render_decl(d: Definition) -> None:
text = '<pre><code class="language-cpp">' text = '<pre><code class="language-cpp">'
@ -131,6 +131,7 @@ class CxxHandler(BaseHandler):
cmd = ['doxygen', '-'] cmd = ['doxygen', '-']
doc_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) doc_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
include_dir = os.path.join(os.path.dirname(doc_dir), 'include', 'fmt') include_dir = os.path.join(os.path.dirname(doc_dir), 'include', 'fmt')
self._ns2doxyxml = {}
self._doxyxml_dir = 'doxyxml' self._doxyxml_dir = 'doxyxml'
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT) p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
out, _ = p.communicate(input=r''' out, _ = p.communicate(input=r'''
@ -173,18 +174,26 @@ class CxxHandler(BaseHandler):
if p.returncode != 0: if p.returncode != 0:
raise CalledProcessError(p.returncode, cmd) 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: def collect(self, identifier: str, config: Mapping[str, Any]) -> Definition:
name = identifier qual_name = 'fmt::' + identifier
paren = name.find('(')
param_str = None param_str = None
paren = qual_name.find('(')
if paren > 0: if paren > 0:
name, param_str = name[:paren], name[paren + 1:-1] qual_name, param_str = qual_name[:paren], qual_name[paren + 1:-1]
nodes = self._doxyxml.findall( 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}']/..") f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
candidates = [] candidates = []
for node in nodes: for node in nodes:
@ -206,13 +215,14 @@ class CxxHandler(BaseHandler):
return d return d
# Process a compound definition such as a struct. # 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: if not cls:
raise Exception(f'Cannot find {identifier}. Candidates: {candidates}') 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) xml = et.parse(f)
node = xml.find('compounddef') node = xml.find('compounddef')
d = Definition(name, node.get('kind')) d = Definition(identifier, node.get('kind'))
d.template_params = convert_template_params(node) d.template_params = convert_template_params(node)
d.desc = get_description(node) d.desc = get_description(node)
d.members = [] d.members = []