Libenvpp
A modern C++ library for type-safe environment variable parsing
Install / Use
/learn @ph3at/LibenvppREADME
libenvpp - Modern C++ Library for Handling Environment Variables
This library provides a modern, platform independent, type-safe way of handling environment variables. It offers an easy to use interface for the most common use-cases, and is extensible and customizable to more complex use-cases, if and when required.
Features
- Platform independent
- Type-safe parsing of environment variables
- Support for required and optional environment variables, with specifiable default value
- Automatic parsing of built-in types
- User-defined types parsable with user-defined parser
- Optional user-defined validation
- Convenience range/option environment variable type
- Parsing/validating possible per type, or per environment variable
- Typo detection based on edit-distance
- Unused environment variable detection
Usage
Simple Example
To use the library the first step would be to include the header:
#include <libenvpp/env.hpp>
The library is built around the idea of a prefix. The assumption is that all relevant environment variables used by a program share the same prefix, for example with the prefix MYPROG:
MYPROG_LOG_FILE_PATH="/path/to/log"
MYPROG_NUM_THREADS=4
This would be expressed as:
auto pre = env::prefix("MYPROG");
const auto log_path_id = pre.register_variable<std::filesystem::path>("LOG_FILE_PATH");
const auto num_threads_id = pre.register_required_variable<unsigned int>("NUM_THREADS");
Here MYPROG_LOG_FILE_PATH is registered as an optional environment variable, which will not cause an error or warning when it is not specified in the environment. Whereas MYPROG_NUM_THREADS is registered as a required environment variable, which will cause an error if it is not found in the environment.
After having registered all variables with the corresponding prefix it can be parsed and validated, which will retrieve the values from the environment and parse them into the type specified when registering:
const auto parsed_and_validated_pre = pre.parse_and_validate();
This will invalidate the prefix pre which must not be used after this point, and can be safely destroyed.
If anything went wrong, like the required variable not being found, or the variable not being parsable, this can be queried through the parsed_and_validated_pre:
if (!parsed_and_validated_pre.ok()) {
std::cout << parsed_and_validated_pre.warning_message();
std::cout << parsed_and_validated_pre.error_message();
}
Otherwise the parsed values can be retrieved from the parsed_and_validated_pre:
if (parsed_and_validated_pre.ok()) {
const auto log_path = parsed_and_validated_pre.get_or(log_path_id, "/default/log/path");
const auto num_threads = parsed_and_validated_pre.get(num_threads_id);
}
Optional variables can be given a default value when getting them with get_or, which will return the default value if (and only if) the variable was not found in the environment. Parsing or validation errors of optional variables are also reported as errors. Additionally, optional variables can also be retrieved with get which will return a std::optional which is empty if the variable was not found in the environment.
Required variables can only be gotten with get, as they are required and therefore need no default value, which will return the type directly. If required variables were not found in the environment this will be reported as an error.
Simple Example - Code
Putting everything together:
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <libenvpp/env.hpp>
int main()
{
auto pre = env::prefix("MYPROG");
const auto log_path_id = pre.register_variable<std::filesystem::path>("LOG_FILE_PATH");
const auto num_threads_id = pre.register_required_variable<unsigned int>("NUM_THREADS");
const auto parsed_and_validated_pre = pre.parse_and_validate();
if (parsed_and_validated_pre.ok()) {
const auto log_path = parsed_and_validated_pre.get_or(log_path_id, "/default/log/path");
const auto num_threads = parsed_and_validated_pre.get(num_threads_id);
std::cout << "Log path : " << log_path << std::endl;
std::cout << "Num threads: " << num_threads << std::endl;
} else {
std::cout << parsed_and_validated_pre.warning_message();
std::cout << parsed_and_validated_pre.error_message();
}
return EXIT_SUCCESS;
}
Simple Example - Output
Running the example without having any environment variable set:
$ ./libenvpp_simple_usage_example
Error : Environment variable 'MYPROG_NUM_THREADS' not set
Running the example with a typo:
$ MYPROG_LOG_FILEPATH=/var/log/file ./libenvpp_simple_usage_example
Warning: Unrecognized environment variable 'MYPROG_LOG_FILEPATH' set, did you mean 'MYPROG_LOG_FILE_PATH'?
Error : Environment variable 'MYPROG_NUM_THREADS' not set
Running the example with only the required variable set:
$ MYPROG_NUM_THREADS=4 ./libenvpp_simple_usage_example
Log path : "/default/log/path"
Num threads: 4
Running the example with both variables set:
$ MYPROG_LOG_FILE_PATH=/var/log/file MYPROG_NUM_THREADS=4 ./libenvpp_simple_usage_example
Log path : "/var/log/file"
Num threads: 4
Custom Type Parser
To provide a parser for a user-defined type it is necessary to specialize the template struct env::default_parser for the specific type that should be parsed and provide a call operator that takes a std::string_view and returns the parsed type, for example:
struct program_data {
int number;
float percent;
};
namespace env {
template <>
struct default_parser<program_data> {
program_data operator()(const std::string_view str) const
{
const auto split_str = split(str, ',');
if (split_str.size() != 2) {
// Report an error if the input does not have the expected format
throw parser_error{"Expected 2 comma delimited values"};
}
auto parsed_data = program_data{};
// Delegate parsing of primitive types to the default_parser
parsed_data.number = default_parser<int>{}(split_str[0]);
parsed_data.percent = default_parser<float>{}(split_str[1]);
return parsed_data;
}
};
} // namespace env
Note: The default_parser already supports primitive types (and everything that can be constructed from string), so parsing should be delegated to the existing implementation whenever possible.
Custom Type Parser - Code
For the entire code see examples/libenvpp_custom_parser_example.cpp.
Custom Type Validator
Similarly to the env::default_parser there is also an env::default_validator which can be specialized for any type that should be validated. The non-specialized implementation of default_validator does nothing, as everything that can be parsed is considered valid by default. When specializing default_validator for a type the struct must contain a call operator that takes the parsed type and returns void. Validation errors should be reported by throwing an env::validation_error:
namespace env {
template <>
struct default_validator<std::filesystem::path> {
void operator()(const std::filesystem::path& path) const
{
if (!std::filesystem::exists(path)) {
throw validation_error{path.string() + " path does not exist"};
}
}
};
} // namespace env
The validator example above shows a validator for the type std::filesystem::path which requires that environment variables of that type must exist on the filesystem, otherwise the specified environment variable is considered invalid, even if the string was a valid path.
Custom Type Validator - Code
For the full example see examples/libenvpp_custom_validator_example.cpp.
Custom Variable Parser and Validator
If parsing/validator should be done in a specific way for a specific variable only, and not for every variable of the same type, it is possible to specify a parser and validator function when registering a variable:
std::filesystem::path path_parser_and_validator(const std::string_view str)
{
const auto log_path = std::filesystem::path(str);
if (!std::filesystem::exists(log_path)) {
if (!std::filesystem::create_directory(log_path)) {
throw env::validation_error{"Unable to create log directory"};
}
} else if (!std::filesystem::is_directory(log_path)) {
throw env::validation_error{"Log path is not a directory"};
}
return log_path;
}
int main()
{
auto pre = env::prefix("CUSTOM_PARSER_AND_VALIDATOR");
const auto path_id = pre.register_required_variable<std::filesystem::path>("LOG_PATH", path_parser_and_validator);
/*...*/
}
For example, the parser and validator function above will make sure that the log path specified by the environment variable points to a directory, and will
Related Skills
node-connect
347.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
107.8kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
347.0kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
347.0kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
