/// \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 /// libpom++ namespace 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. /// /// Currently supported languages: `en`, `fr`. /// /// \param lang IETF-like language tag. /// The closest supported language will be used /// (e.g. `fr-CA` will currently redirect to `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`. /// /// This method will not be called excessively/with lots of tiny reads—it's /// okay to do unbuffered reads in it. /// /// \param buf Place to put data. /// \param count (Non-zero) number of bytes to read /// /// \returns 0 to indicate the end of the file, /// or a positive value ≤ `count`, to indicate the number of bytes read. virtual size_t read(char *buf, size_t count) = 0; }; /// An item in a configuration (returned by \ref Configuration::items). class Item { public: /// Name of the key. /// /// The returned string view is valid for the lifetime of `this`. std::string_view key() const noexcept { return m_key; } /// Value of the key. /// /// The returned string view is valid for the lifetime of `this`. std::string_view value() const noexcept { return m_value; } /// File name where key was defined. /// /// The returned string view is valid for the lifetime of `this`. std::string_view file() const noexcept { return m_file; } /// Line number where key was defined. uint64_t line() const noexcept { return m_line; } private: Item(std::string_view key, std::string_view value, std::string_view file, uint64_t line): m_key(key), m_value(value), m_file(file), m_line(line) {} friend class Configuration; std::string m_key, m_value, m_file; uint64_t m_line; }; /// A source location. class Location { public: /// File name /// /// The returned string view is valid for the lifetime of `this`. inline std::string_view file() const { return m_file; } /// Line number 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; }; /// Print location as `file``:``line` (exact format may change in future). std::ostream &operator<<(std::ostream &, const Location &); /// A POM configuration. class Configuration { public: /// Create empty configuration. Configuration(); /// Set `this` to `other`, deleting the current configuration in `this`. Configuration &operator=(const Configuration &other); /// Copy configuration from `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. /// /// \param filename File name for error messages. /// \param source Abstract reader to get file data from. /// \param settings Settings for parsing the file, or `nullptr` to use defaults. Configuration(std::string_view filename, Reader &source, const Settings *settings = nullptr); /// Load configuration from a `std::istream`. /// /// \param filename File name for error messages. /// \param stream Stream to read file from. /// \param settings Settings for parsing the file, or `nullptr` to use defaults. Configuration(std::string_view filename, std::istream &stream, const Settings *settings = nullptr); /// Load configuration from a file path. /// /// \param path File name for error messages and to load configuration from. /// \param settings Settings for parsing the file, or `nullptr` to use defaults. Configuration(std::string_view path, const Settings *settings = nullptr); /// Load configuration from a string. /// /// \param filename File name for error messages. /// \param string String containing the configuration. /// \param settings Settings for parsing the file, or `nullptr` to use defaults. Configuration(std::string_view filename, std::string_view string, const Settings *settings = nullptr); ~Configuration(); /// Get value of `key` in configuration. std::optional get(std::string_view key) const; /// Get value of `key` in configuration, or `dflt` if `key` isn't defined. std::string get_or_default(std::string_view key, std::string_view dflt) const; /// Get signed integer value of `key` in configuration. /// /// Throws an \ref Error if `key` exists but isn't a valid signed integer. std::optional get_int(std::string_view key) const; /// Get signed integer value of `key` in configuration, or `dflt` if `key` isn't defined. /// /// Throws an \ref Error if `key` exists but isn't a valid signed integer. int64_t get_int_or_default(std::string_view key, int64_t dflt) const; /// Get unsigned integer value of `key` in configuration. /// /// Throws an \ref Error if `key` exists but isn't a valid unsigned integer. std::optional get_uint(std::string_view key) const; /// Get unsigned integer value of `key` in configuration, or `dflt` if `key` isn't defined. /// /// Throws an \ref Error if `key` exists but isn't a valid unsigned integer. uint64_t get_uint_or_default(std::string_view key, uint64_t dflt) const; /// Get floating-point value of `key` in configuration. /// /// Throws an \ref Error if `key` exists but isn't a valid floating-point number. std::optional get_float(std::string_view key) const; /// Get floating-point value of `key` in configuration, or `dflt` if `key` isn't defined. /// /// Throws an \ref Error if `key` exists but isn't a valid floating-point number. double get_float_or_default(std::string_view key, double dflt) const; /// Get boolean value of `key` in configuration. /// /// Throws an \ref Error if `key` exists but isn't a valid boolean (`on`/`off`/`yes`/`no`/`true`/`false`). std::optional get_bool(std::string_view key) const; /// Get boolean value of `key` in configuration, or `dflt` if `key` isn't defined. /// /// Throws an \ref Error if `key` exists but isn't a valid boolean (`on`/`off`/`yes`/`no`/`true`/`false`). bool get_bool_or_default(std::string_view key, bool dflt) const; /// Get list value of `key` in configuration. std::optional> get_list(std::string_view key) const; /// Get list value of `key` in configuration, or `dflt` if `key` isn't defined. std::vector get_list_or_default(std::string_view key, const std::vector &dflt) const; /// Returns whether `key` is in this configuration. bool has(std::string_view key) const; /// Returns location of `key` in this configuration. std::optional location(std::string_view key) const; /// Extract section of configuration consisting of all keys starting with `name.` and their values. 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; /// Get list of all "direct" keys (unique first components of keys) in this configuration. std::vector keys() const; /// Get all key-value pairs in this configuration. /// /// This returns a vector of pointers, so that more fields can be added to items in the future /// without breaking binary backwards compatibility. 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; }; /// Print configuration. std::ostream &operator<<(std::ostream &, const Configuration &); } // namespace pom #endif // POM_HPP_