summaryrefslogtreecommitdiff
path: root/cpp/pom.hpp
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2025-09-16 20:49:49 -0400
committerpommicket <pommicket@gmail.com>2025-09-16 20:52:22 -0400
commitea7b73aac55177d1d556d0c9dba04b0870d3aaf6 (patch)
tree98862ab518680573c4d1d77542bc88b5ec9ceb2a /cpp/pom.hpp
parent62bb1ffdee060819657161e260e75e3e1df017ac (diff)
Allow short reads from read_func
Diffstat (limited to 'cpp/pom.hpp')
-rw-r--r--cpp/pom.hpp105
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);