/// \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_ #include #include #include #include #include #include #include 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; /// 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; friend class Parser; Error() = delete; Error(void *C_error); Error(const void *C_error); void *C; bool m_is_original; std::unique_ptr m_next; }; /// Print error. std::ostream &operator<<(std::ostream &, Error &); /// Settings for configuration parsing. class Settings { public: /// 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] = {}; // 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; }; /// An item in a configuration /// /// This is an abstract class so that items can be given /// more members in the future. class Item { public: inline virtual ~Item() {}; virtual std::string_view key() const noexcept = 0; virtual std::string_view value() const noexcept = 0; virtual std::string_view file() const noexcept = 0; virtual uint64_t line() const noexcept = 0; }; class Location { public: inline std::string_view file() const { return m_file; } inline uint64_t line() const { return m_line; } private: inline Location(std::string file, uint64_t line): m_file(file), m_line(line) {} friend class Configuration; std::string m_file; uint64_t m_line; // to allow for future extensions without breaking backwards compatibility const uint32_t version = 1; }; std::ostream &operator<<(std::ostream &, const Location &); class Configuration { 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); Configuration(std::string_view filename, std::string_view string, const Settings *settings = nullptr); ~Configuration(); std::optional location(std::string_view key) const; std::optional get(std::string_view key) const; std::string get_or_default(std::string_view key, std::string_view dflt) const; std::optional get_int(std::string_view key) const; int64_t get_int_or_default(std::string_view key, int64_t dflt) const; std::optional get_uint(std::string_view key) const; uint64_t get_uint_or_default(std::string_view key, uint64_t dflt) const; std::optional get_float(std::string_view key) const; double get_float_or_default(std::string_view key, double dflt) const; std::optional get_bool(std::string_view key) const; bool get_bool_or_default(std::string_view key, bool dflt) const; std::optional> get_list(std::string_view key) const; std::vector get_list_or_default(std::string_view key, const std::vector &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 unread_keys() const; std::vector keys() const; std::vector> 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); explicit Configuration(void *c): C(c) {} void *C; }; std::ostream &operator<<(std::ostream &, const Configuration &); } // namespace pom #endif // POM_HPP_