diff options
author | pommicket <pommicket@gmail.com> | 2025-09-16 20:49:49 -0400 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2025-09-16 20:52:22 -0400 |
commit | ea7b73aac55177d1d556d0c9dba04b0870d3aaf6 (patch) | |
tree | 98862ab518680573c4d1d77542bc88b5ec9ceb2a /cpp/pom.hpp | |
parent | 62bb1ffdee060819657161e260e75e3e1df017ac (diff) |
Allow short reads from read_func
Diffstat (limited to 'cpp/pom.hpp')
-rw-r--r-- | cpp/pom.hpp | 105 |
1 files changed, 87 insertions, 18 deletions
diff --git a/cpp/pom.hpp b/cpp/pom.hpp index 69a979d..4e20727 100644 --- a/cpp/pom.hpp +++ b/cpp/pom.hpp @@ -1,3 +1,36 @@ +/// \file +/// POM configuration parser for C++. +/// +/// ## Thread-safety +/// +/// Of course, you should not \ref pom::Configuration::merge into +/// a configuration while another thread is using it. +/// +/// Otherwise, libpom is fully thread-safe +/// provided that C11 atomics are available +/// (`__STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)`). +/// But beware of race conditions when using \ref pom::Configuration::unread_keys +/// — this will not lead to UB but you may get unexpected results — +/// ensure there is synchronization so that all threads +/// have certainly read their keys before calling it. +/// +/// If C11 atomics are not available, you can almost certainly still get away +/// with sharing configurations across threads, as long as you use proper +/// synchronization for \ref pom::Configuration::unread_keys. +/// (Essentially, libpom may end up writing the same value to the same address +/// from separate threads, which is *technically* undefined behaviour, but will +/// likely never be an issue on any real machine.) +/// Even if you are extremely paranoid, you can still use +/// distinct configurations in different threads without worry. +/// +/// ## Notes +/// +/// Every libpom++ function may change the value of `errno` arbitrarily +/// (its value after any libpom++ call should be ignored). + +/// \mainpage libpom++ doxygen documentation +/// +/// See \ref pom.hpp for all types/functions. #ifndef POM_HPP_ #define POM_HPP_ @@ -11,18 +44,41 @@ namespace pom { +/// A libpom++ error. class Error: public std::exception { public: ~Error(); Error(Error &other) = delete; + /// Get file where error occured. + /// + /// Returned string view lives for as long as `this`. std::string_view file() const noexcept; + /// Get line number where error occurred. uint64_t line() const noexcept; + /// Get error message. You probably want \ref to_string instead. + /// + /// This only gets a single error message from this entry in an error list, + /// and doesn't include the file name or line number. + /// + /// Returned string view lives for as long as `this`. std::string_view message() const noexcept; + /// Get next error + /// + /// Returned pointer lives for as long as `this`. inline const Error *next() const noexcept { return m_next.get(); } + /// Get description of error. + /// + /// Returned string view lives for as long as `this`. std::string_view to_string() noexcept; - /// You should only call this on the first error in an error list. - /// (This can't be enforced with constness because it needs to - /// override `std::exception::what`.) + /// Get description of error (equivalent to \ref to_string — use that instead if you can). + /// + /// You should only call this on the first error in an error list + /// (i.e. don't call it on an error gotten from \ref next); + /// otherwise you will get a valid but mostly useless string. + /// (Unlike \ref to_string, this can't be enforced with + /// constness because this method needs to override `std::exception::what`.) + /// + /// Returned pointer lives for as long as `this`. virtual const char *what() const noexcept override; private: friend class Configuration; @@ -34,36 +90,38 @@ private: bool m_is_original; std::unique_ptr<const Error> m_next; }; +/// Print error. std::ostream &operator<<(std::ostream &, Error &); -class Allocator { -public: - inline virtual ~Allocator() {}; - virtual void *calloc(size_t, size_t) = 0; - virtual void *realloc(void *, size_t) = 0; - virtual void free(void *) = 0; -}; - +/// Settings for configuration parsing. class Settings { public: - inline Settings() {}; - /// Set allocator. - inline void set_allocator(std::shared_ptr<Allocator> allocator) { - m_allocator = allocator; - } + /// Default settings. + inline Settings() {} + /// Set language for error messages. + /// + /// `lang` should be an IETF-like language tag. + /// + /// The closest supported language will be used + /// (e.g. `fr-CA` will currently redirect to `fr`). + /// + /// Currently supported: `en`, `fr`. void set_error_language(std::string_view lang); private: void check_version() const; void to_C(void *C) const; friend class Configuration; char m_error_lang[16] = {}; - std::shared_ptr<Allocator> m_allocator; // to allow for future extensions without breaking backwards compatibility const uint32_t version = 1; }; +/// Abstract base class for a file reader. +/// +/// This can be passed to \ref Configuration::Configuration(std::string_view, Reader &, const Settings *) class Reader { public: + /// Read up to `count` bytes of data into `buf`. virtual size_t read(char *buf, size_t count) = 0; }; @@ -90,7 +148,8 @@ private: friend class Configuration; std::string m_file; uint64_t m_line; - void *_reserved[4] = {}; + // to allow for future extensions without breaking backwards compatibility + const uint32_t version = 1; }; std::ostream &operator<<(std::ostream &, const Location &); @@ -99,6 +158,10 @@ public: Configuration(); Configuration &operator=(const Configuration &other); inline Configuration(const Configuration &other) { *this = other; }; + /// Load configuration from abstract \ref Reader. + /// + /// Most of the time, you will be able to use another constructor to load a configuration. + /// But if you have special functions for performing reads, you may need this. Configuration(std::string_view filename, Reader &source, const Settings *settings = nullptr); Configuration(std::string_view filename, std::istream &stream, const Settings *settings = nullptr); Configuration(std::string_view path, const Settings *settings = nullptr); @@ -118,9 +181,15 @@ public: std::optional<std::vector<std::string>> get_list(std::string_view key) const; std::vector<std::string> get_list_or_default(std::string_view key, const std::vector<std::string> &dflt) const; Configuration section(std::string_view name) const; + /// Get list of keys which haven't been the target of a `get_*` method. std::vector<std::string> unread_keys() const; std::vector<std::string> keys() const; std::vector<std::shared_ptr<Item>> items() const; + /// Merge `other` configuration into `this`. + /// + /// Puts all the key-value pairs of `other` into this configuration. + /// If `this` and `other` both have a value for a key, the one + /// in `other` is preferred. void merge(const Configuration &other); private: void load(std::string_view filename, Reader &source, const Settings *settings); |